diff options
author | robot-piglet <[email protected]> | 2025-02-08 20:17:29 +0300 |
---|---|---|
committer | robot-piglet <[email protected]> | 2025-02-08 20:32:11 +0300 |
commit | 6b7c255668de517dff6462bd377d345d240f8a67 (patch) | |
tree | 1bde953b7f2b6d9e8efd72a0ceebfa0a791a024c | |
parent | 2309a9980fd82ba7df5a21876c790e7e4d776ded (diff) |
Intermediate changes
commit_hash:f4cb1bdccfb534d71b7f461fc8f8e5656c47bfa5
15 files changed, 334 insertions, 19 deletions
diff --git a/contrib/python/fonttools/.dist-info/METADATA b/contrib/python/fonttools/.dist-info/METADATA index 931191c2892..68bd23ea996 100644 --- a/contrib/python/fonttools/.dist-info/METADATA +++ b/contrib/python/fonttools/.dist-info/METADATA @@ -1,6 +1,6 @@ Metadata-Version: 2.2 Name: fonttools -Version: 4.55.4 +Version: 4.55.6 Summary: Tools to manipulate font files Home-page: http://github.com/fonttools/fonttools Author: Just van Rossum @@ -389,6 +389,17 @@ Have fun! Changelog ~~~~~~~~~ +4.55.6 (released 2025-01-24) +---------------------------- + +- [glyf] Fixed regression introduced in 4.55.5 when computing bounds of nested composite glyphs with transformed components (#3752). + +4.55.5 (released 2025-01-23) +---------------------------- + +- [glyf] Fixed recalcBounds of transformed components with unrounded coordinates (#3750). +- [feaLib] Allow duplicate script/language statements (#3749). + 4.55.4 (released 2025-01-21) ---------------------------- diff --git a/contrib/python/fonttools/fontTools/__init__.py b/contrib/python/fonttools/fontTools/__init__.py index d1af15d948f..83406338269 100644 --- a/contrib/python/fonttools/fontTools/__init__.py +++ b/contrib/python/fonttools/fontTools/__init__.py @@ -3,6 +3,6 @@ from fontTools.misc.loggingTools import configLogger log = logging.getLogger(__name__) -version = __version__ = "4.55.4" +version = __version__ = "4.55.6" __all__ = ["version", "log", "configLogger"] diff --git a/contrib/python/fonttools/fontTools/feaLib/builder.py b/contrib/python/fonttools/fontTools/feaLib/builder.py index 1cfe1c3a137..a6ba8f2f612 100644 --- a/contrib/python/fonttools/fontTools/feaLib/builder.py +++ b/contrib/python/fonttools/fontTools/feaLib/builder.py @@ -1106,7 +1106,13 @@ class Builder(object): if (language == "dflt" or include_default) and lookups: self.features_[key] = lookups[:] else: - self.features_[key] = [] + # if we aren't including default we need to manually remove the + # default lookups, which were added to all declared langsystems + # as they were encountered (we don't remove all lookups because + # we want to allow duplicate script/lang statements; + # see https://github.com/fonttools/fonttools/issues/3748 + cur_lookups = self.features_.get(key, []) + self.features_[key] = [x for x in cur_lookups if x not in lookups] self.language_systems = frozenset([(self.script_, language)]) if required: diff --git a/contrib/python/fonttools/fontTools/ttLib/tables/_g_l_y_f.py b/contrib/python/fonttools/fontTools/ttLib/tables/_g_l_y_f.py index 92b69e70b1f..eaa9920f008 100644 --- a/contrib/python/fonttools/fontTools/ttLib/tables/_g_l_y_f.py +++ b/contrib/python/fonttools/fontTools/ttLib/tables/_g_l_y_f.py @@ -1187,7 +1187,7 @@ class Glyph(object): ): return try: - coords, endPts, flags = self.getCoordinates(glyfTable) + coords, endPts, flags = self.getCoordinates(glyfTable, round=otRound) self.xMin, self.yMin, self.xMax, self.yMax = coords.calcIntBounds() except NotImplementedError: pass @@ -1206,9 +1206,7 @@ class Glyph(object): Return True if bounds were calculated, False otherwise. """ for compo in self.components: - if hasattr(compo, "firstPt") or hasattr(compo, "transform"): - return False - if not float(compo.x).is_integer() or not float(compo.y).is_integer(): + if not compo._hasOnlyIntegerTranslate(): return False # All components are untransformed and have an integer x/y translate @@ -1241,7 +1239,7 @@ class Glyph(object): else: return self.numberOfContours == -1 - def getCoordinates(self, glyfTable): + def getCoordinates(self, glyfTable, *, round=noRound): """Return the coordinates, end points and flags This method returns three values: A :py:class:`GlyphCoordinates` object, @@ -1267,13 +1265,27 @@ class Glyph(object): for compo in self.components: g = glyfTable[compo.glyphName] try: - coordinates, endPts, flags = g.getCoordinates(glyfTable) + coordinates, endPts, flags = g.getCoordinates( + glyfTable, round=round + ) except RecursionError: raise ttLib.TTLibError( "glyph '%s' contains a recursive component reference" % compo.glyphName ) coordinates = GlyphCoordinates(coordinates) + # if asked to round e.g. while computing bboxes, it's important we + # do it immediately before a component transform is applied to a + # simple glyph's coordinates in case these might still contain floats; + # however, if the referenced component glyph is another composite, we + # must not round here but only at the end, after all the nested + # transforms have been applied, or else rounding errors will compound. + if ( + round is not noRound + and g.numberOfContours > 0 + and not compo._hasOnlyIntegerTranslate() + ): + coordinates.toInt(round=round) if hasattr(compo, "firstPt"): # component uses two reference points: we apply the transform _before_ # computing the offset between the points @@ -1930,6 +1942,18 @@ class GlyphComponent(object): result = self.__eq__(other) return result if result is NotImplemented else not result + def _hasOnlyIntegerTranslate(self): + """Return True if it's a 'simple' component. + + That is, it has no anchor points and no transform other than integer translate. + """ + return ( + not hasattr(self, "firstPt") + and not hasattr(self, "transform") + and float(self.x).is_integer() + and float(self.y).is_integer() + ) + class GlyphCoordinates(object): """A list of glyph coordinates. diff --git a/contrib/python/fonttools/ya.make b/contrib/python/fonttools/ya.make index 461b5646645..9ada57d3f69 100644 --- a/contrib/python/fonttools/ya.make +++ b/contrib/python/fonttools/ya.make @@ -2,7 +2,7 @@ PY3_LIBRARY() -VERSION(4.55.4) +VERSION(4.55.6) LICENSE(MIT) diff --git a/contrib/python/google-auth/py3/.dist-info/METADATA b/contrib/python/google-auth/py3/.dist-info/METADATA index f1724bbf73f..952e020e3cd 100644 --- a/contrib/python/google-auth/py3/.dist-info/METADATA +++ b/contrib/python/google-auth/py3/.dist-info/METADATA @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: google-auth -Version: 2.37.0 +Version: 2.38.0 Summary: Google Authentication Library Home-page: https://github.com/googleapis/google-auth-library-python Author: Google Cloud Platform diff --git a/contrib/python/google-auth/py3/google/auth/_default.py b/contrib/python/google-auth/py3/google/auth/_default.py index cdc8b7a6460..1234fb25d78 100644 --- a/contrib/python/google-auth/py3/google/auth/_default.py +++ b/contrib/python/google-auth/py3/google/auth/_default.py @@ -85,6 +85,17 @@ def load_credentials_from_file( user credentials, external account credentials, or impersonated service account credentials. + .. warning:: + Important: If you accept a credential configuration (credential JSON/File/Stream) + from an external source for authentication to Google Cloud Platform, you must + validate it before providing it to any Google API or client library. Providing an + unvalidated credential configuration to Google APIs or libraries can compromise + the security of your systems and data. For more information, refer to + `Validate credential configurations from external sources`_. + + .. _Validate credential configurations from external sources: + https://cloud.google.com/docs/authentication/external/externally-sourced-credentials + Args: filename (str): The full path to the credentials file. scopes (Optional[Sequence[str]]): The list of scopes for the credentials. If @@ -137,6 +148,17 @@ def load_credentials_from_dict( user credentials, external account credentials, or impersonated service account credentials. + .. warning:: + Important: If you accept a credential configuration (credential JSON/File/Stream) + from an external source for authentication to Google Cloud Platform, you must + validate it before providing it to any Google API or client library. Providing an + unvalidated credential configuration to Google APIs or libraries can compromise + the security of your systems and data. For more information, refer to + `Validate credential configurations from external sources`_. + + .. _Validate credential configurations from external sources: + https://cloud.google.com/docs/authentication/external/externally-sourced-credentials + Args: info (Dict[str, Any]): A dict object containing the credentials scopes (Optional[Sequence[str]]): The list of scopes for the credentials. If diff --git a/contrib/python/google-auth/py3/google/auth/compute_engine/_metadata.py b/contrib/python/google-auth/py3/google/auth/compute_engine/_metadata.py index 8d692972fd6..06f99de0e2c 100644 --- a/contrib/python/google-auth/py3/google/auth/compute_engine/_metadata.py +++ b/contrib/python/google-auth/py3/google/auth/compute_engine/_metadata.py @@ -201,7 +201,7 @@ def get( url = _helpers.update_query(base_url, query_params) backoff = ExponentialBackoff(total_attempts=retry_count) - + failure_reason = None for attempt in backoff: try: response = request(url=url, method="GET", headers=headers_to_use) @@ -213,6 +213,11 @@ def get( retry_count, response.status, ) + failure_reason = ( + response.data.decode("utf-8") + if hasattr(response.data, "decode") + else response.data + ) continue else: break @@ -225,10 +230,13 @@ def get( retry_count, e, ) + failure_reason = e else: raise exceptions.TransportError( "Failed to retrieve {} from the Google Compute Engine " - "metadata service. Compute Engine Metadata server unavailable".format(url) + "metadata service. Compute Engine Metadata server unavailable due to {}".format( + url, failure_reason + ) ) content = _helpers.from_bytes(response.data) diff --git a/contrib/python/google-auth/py3/google/auth/iam.py b/contrib/python/google-auth/py3/google/auth/iam.py index dcf0dbf9d58..1e4cdffec18 100644 --- a/contrib/python/google-auth/py3/google/auth/iam.py +++ b/contrib/python/google-auth/py3/google/auth/iam.py @@ -48,6 +48,11 @@ _IAM_SIGN_ENDPOINT = ( + "/serviceAccounts/{}:signBlob" ) +_IAM_SIGNJWT_ENDPOINT = ( + "https://iamcredentials.googleapis.com/v1/projects/-" + + "/serviceAccounts/{}:signJwt" +) + _IAM_IDTOKEN_ENDPOINT = ( "https://iamcredentials.googleapis.com/v1/" + "projects/-/serviceAccounts/{}:generateIdToken" diff --git a/contrib/python/google-auth/py3/google/auth/impersonated_credentials.py b/contrib/python/google-auth/py3/google/auth/impersonated_credentials.py index d51c8ef1e85..ed7e3f00b1c 100644 --- a/contrib/python/google-auth/py3/google/auth/impersonated_credentials.py +++ b/contrib/python/google-auth/py3/google/auth/impersonated_credentials.py @@ -38,12 +38,15 @@ from google.auth import exceptions from google.auth import iam from google.auth import jwt from google.auth import metrics +from google.oauth2 import _client _REFRESH_ERROR = "Unable to acquire impersonated credentials" _DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds +_GOOGLE_OAUTH2_TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token" + def _make_iam_token_request( request, @@ -177,6 +180,7 @@ class Credentials( target_principal, target_scopes, delegates=None, + subject=None, lifetime=_DEFAULT_TOKEN_LIFETIME_SECS, quota_project_id=None, iam_endpoint_override=None, @@ -204,9 +208,12 @@ class Credentials( quota_project_id (Optional[str]): The project ID used for quota and billing. This project may be different from the project used to create the credentials. - iam_endpoint_override (Optiona[str]): The full IAM endpoint override + iam_endpoint_override (Optional[str]): The full IAM endpoint override with the target_principal embedded. This is useful when supporting impersonation with regional endpoints. + subject (Optional[str]): sub field of a JWT. This field should only be set + if you wish to impersonate as a user. This feature is useful when + using domain wide delegation. """ super(Credentials, self).__init__() @@ -231,6 +238,7 @@ class Credentials( self._target_principal = target_principal self._target_scopes = target_scopes self._delegates = delegates + self._subject = subject self._lifetime = lifetime or _DEFAULT_TOKEN_LIFETIME_SECS self.token = None self.expiry = _helpers.utcnow() @@ -275,6 +283,39 @@ class Credentials( # Apply the source credentials authentication info. self._source_credentials.apply(headers) + # If a subject is specified a domain-wide delegation auth-flow is initiated + # to impersonate as the provided subject (user). + if self._subject: + if self.universe_domain != credentials.DEFAULT_UNIVERSE_DOMAIN: + raise exceptions.GoogleAuthError( + "Domain-wide delegation is not supported in universes other " + + "than googleapis.com" + ) + + now = _helpers.utcnow() + payload = { + "iss": self._target_principal, + "scope": _helpers.scopes_to_string(self._target_scopes or ()), + "sub": self._subject, + "aud": _GOOGLE_OAUTH2_TOKEN_ENDPOINT, + "iat": _helpers.datetime_to_secs(now), + "exp": _helpers.datetime_to_secs(now) + _DEFAULT_TOKEN_LIFETIME_SECS, + } + + assertion = _sign_jwt_request( + request=request, + principal=self._target_principal, + headers=headers, + payload=payload, + delegates=self._delegates, + ) + + self.token, self.expiry, _ = _client.jwt_grant( + request, _GOOGLE_OAUTH2_TOKEN_ENDPOINT, assertion + ) + + return + self.token, self.expiry = _make_iam_token_request( request=request, principal=self._target_principal, @@ -478,3 +519,61 @@ class IDTokenCredentials(credentials.CredentialsWithQuotaProject): self.expiry = datetime.utcfromtimestamp( jwt.decode(id_token, verify=False)["exp"] ) + + +def _sign_jwt_request(request, principal, headers, payload, delegates=[]): + """Makes a request to the Google Cloud IAM service to sign a JWT using a + service account's system-managed private key. + Args: + request (Request): The Request object to use. + principal (str): The principal to request an access token for. + headers (Mapping[str, str]): Map of headers to transmit. + payload (Mapping[str, str]): The JWT payload to sign. Must be a + serialized JSON object that contains a JWT Claims Set. + delegates (Sequence[str]): The chained list of delegates required + to grant the final access_token. If set, the sequence of + identities must have "Service Account Token Creator" capability + granted to the prceeding identity. For example, if set to + [serviceAccountB, serviceAccountC], the source_credential + must have the Token Creator role on serviceAccountB. + serviceAccountB must have the Token Creator on + serviceAccountC. + Finally, C must have Token Creator on target_principal. + If left unset, source_credential must have that role on + target_principal. + + Raises: + google.auth.exceptions.TransportError: Raised if there is an underlying + HTTP connection error + google.auth.exceptions.RefreshError: Raised if the impersonated + credentials are not available. Common reasons are + `iamcredentials.googleapis.com` is not enabled or the + `Service Account Token Creator` is not assigned + """ + iam_endpoint = iam._IAM_SIGNJWT_ENDPOINT.format(principal) + + body = {"delegates": delegates, "payload": json.dumps(payload)} + body = json.dumps(body).encode("utf-8") + + response = request(url=iam_endpoint, method="POST", headers=headers, body=body) + + # support both string and bytes type response.data + response_body = ( + response.data.decode("utf-8") + if hasattr(response.data, "decode") + else response.data + ) + + if response.status != http_client.OK: + raise exceptions.RefreshError(_REFRESH_ERROR, response_body) + + try: + jwt_response = json.loads(response_body) + signed_jwt = jwt_response["signedJwt"] + return signed_jwt + + except (KeyError, ValueError) as caught_exc: + new_exc = exceptions.RefreshError( + "{}: No signed JWT in response.".format(_REFRESH_ERROR), response_body + ) + raise new_exc from caught_exc diff --git a/contrib/python/google-auth/py3/google/auth/version.py b/contrib/python/google-auth/py3/google/auth/version.py index 06ec7e7fb79..41a80e6c676 100644 --- a/contrib/python/google-auth/py3/google/auth/version.py +++ b/contrib/python/google-auth/py3/google/auth/version.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "2.37.0" +__version__ = "2.38.0" diff --git a/contrib/python/google-auth/py3/tests/compute_engine/test__metadata.py b/contrib/python/google-auth/py3/tests/compute_engine/test__metadata.py index 03ba8de497d..a768b17fa0d 100644 --- a/contrib/python/google-auth/py3/tests/compute_engine/test__metadata.py +++ b/contrib/python/google-auth/py3/tests/compute_engine/test__metadata.py @@ -346,12 +346,32 @@ def test_get_return_none_for_not_found_error(): @mock.patch("time.sleep", return_value=None) def test_get_failure_connection_failed(mock_sleep): request = make_request("") - request.side_effect = exceptions.TransportError() + request.side_effect = exceptions.TransportError("failure message") with pytest.raises(exceptions.TransportError) as excinfo: _metadata.get(request, PATH) - assert excinfo.match(r"Compute Engine Metadata server unavailable") + assert excinfo.match( + r"Compute Engine Metadata server unavailable due to failure message" + ) + + request.assert_called_with( + method="GET", + url=_metadata._METADATA_ROOT + PATH, + headers=_metadata._METADATA_HEADERS, + ) + assert request.call_count == 5 + + +def test_get_too_many_requests_retryable_error_failure(): + request = make_request("too many requests", status=http_client.TOO_MANY_REQUESTS) + + with pytest.raises(exceptions.TransportError) as excinfo: + _metadata.get(request, PATH) + + assert excinfo.match( + r"Compute Engine Metadata server unavailable due to too many requests" + ) request.assert_called_with( method="GET", diff --git a/contrib/python/google-auth/py3/tests/test_impersonated_credentials.py b/contrib/python/google-auth/py3/tests/test_impersonated_credentials.py index 371477b8a9f..0321a1a1d7b 100644 --- a/contrib/python/google-auth/py3/tests/test_impersonated_credentials.py +++ b/contrib/python/google-auth/py3/tests/test_impersonated_credentials.py @@ -72,6 +72,17 @@ def mock_donor_credentials(): yield grant +def mock_dwd_credentials(): + with mock.patch("google.oauth2._client.jwt_grant", autospec=True) as grant: + grant.return_value = ( + "1/fFAGRNJasdfz70BzhT3Zg", + _helpers.utcnow() + datetime.timedelta(seconds=500), + {}, + ) + yield grant + + class MockResponse: def __init__(self, json_data, status_code): self.json_data = json_data @@ -124,6 +135,7 @@ class TestImpersonatedCredentials(object): source_credentials=SOURCE_CREDENTIALS, lifetime=LIFETIME, target_principal=TARGET_PRINCIPAL, + subject=None, iam_endpoint_override=None, ): @@ -133,6 +145,7 @@ class TestImpersonatedCredentials(object): target_scopes=self.TARGET_SCOPES, delegates=self.DELEGATES, lifetime=lifetime, + subject=subject, iam_endpoint_override=iam_endpoint_override, ) @@ -240,6 +253,28 @@ class TestImpersonatedCredentials(object): ) @pytest.mark.parametrize("use_data_bytes", [True, False]) + def test_refresh_with_subject_success(self, use_data_bytes, mock_dwd_credentials): + credentials = self.make_credentials(subject="[email protected]", lifetime=None) + + response_body = {"signedJwt": "example_signed_jwt"} + + request = self.make_request( + data=json.dumps(response_body), + status=http_client.OK, + use_data_bytes=use_data_bytes, + ) + + with mock.patch( + "google.auth.metrics.token_request_access_token_impersonate", + return_value=ACCESS_TOKEN_REQUEST_METRICS_HEADER_VALUE, + ): + credentials.refresh(request) + + assert credentials.valid + assert not credentials.expired + assert credentials.token == "1/fFAGRNJasdfz70BzhT3Zg" + + @pytest.mark.parametrize("use_data_bytes", [True, False]) def test_refresh_success_nonGdu(self, use_data_bytes, mock_donor_credentials): source_credentials = service_account.Credentials( SIGNER, "[email protected]", TOKEN_URI, universe_domain="foo.bar" @@ -419,6 +454,33 @@ class TestImpersonatedCredentials(object): assert not credentials.valid assert credentials.expired + def test_refresh_failure_subject_with_nondefault_domain( + self, mock_donor_credentials + ): + source_credentials = service_account.Credentials( + SIGNER, "[email protected]", TOKEN_URI, universe_domain="foo.bar" + ) + credentials = self.make_credentials( + source_credentials=source_credentials, subject="[email protected]" + ) + + expire_time = (_helpers.utcnow().replace(microsecond=0)).isoformat("T") + "Z" + response_body = {"accessToken": "token", "expireTime": expire_time} + request = self.make_request( + data=json.dumps(response_body), status=http_client.OK + ) + + with pytest.raises(exceptions.GoogleAuthError) as excinfo: + credentials.refresh(request) + + assert excinfo.match( + "Domain-wide delegation is not supported in universes other " + + "than googleapis.com" + ) + + assert not credentials.valid + assert credentials.expired + def test_expired(self): credentials = self.make_credentials(lifetime=None) assert credentials.expired @@ -811,3 +873,61 @@ class TestImpersonatedCredentials(object): id_creds.refresh(request) assert id_creds.quota_project_id == "project-foo" + + def test_sign_jwt_request_success(self): + principal = "[email protected]" + expected_signed_jwt = "correct_signed_jwt" + + response_body = {"keyId": "1", "signedJwt": expected_signed_jwt} + request = self.make_request( + data=json.dumps(response_body), status=http_client.OK + ) + + signed_jwt = impersonated_credentials._sign_jwt_request( + request=request, principal=principal, headers={}, payload={} + ) + + assert signed_jwt == expected_signed_jwt + request.assert_called_once_with( + url="https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/[email protected]:signJwt", + method="POST", + headers={}, + body=json.dumps({"delegates": [], "payload": json.dumps({})}).encode( + "utf-8" + ), + ) + + def test_sign_jwt_request_http_error(self): + principal = "[email protected]" + + request = self.make_request( + data="error_message", status=http_client.BAD_REQUEST + ) + + with pytest.raises(exceptions.RefreshError) as excinfo: + _ = impersonated_credentials._sign_jwt_request( + request=request, principal=principal, headers={}, payload={} + ) + + assert excinfo.match(impersonated_credentials._REFRESH_ERROR) + + assert excinfo.value.args[0] == "Unable to acquire impersonated credentials" + assert excinfo.value.args[1] == "error_message" + + def test_sign_jwt_request_invalid_response_error(self): + principal = "[email protected]" + + request = self.make_request(data="invalid_data", status=http_client.OK) + + with pytest.raises(exceptions.RefreshError) as excinfo: + _ = impersonated_credentials._sign_jwt_request( + request=request, principal=principal, headers={}, payload={} + ) + + assert excinfo.match(impersonated_credentials._REFRESH_ERROR) + + assert ( + excinfo.value.args[0] + == "Unable to acquire impersonated credentials: No signed JWT in response." + ) + assert excinfo.value.args[1] == "invalid_data" diff --git a/contrib/python/google-auth/py3/ya.make b/contrib/python/google-auth/py3/ya.make index 0b08f76a8e0..bb762919dc6 100644 --- a/contrib/python/google-auth/py3/ya.make +++ b/contrib/python/google-auth/py3/ya.make @@ -2,7 +2,7 @@ PY3_LIBRARY() -VERSION(2.37.0) +VERSION(2.38.0) LICENSE(Apache-2.0) diff --git a/yt/yt/client/cache/config.cpp b/yt/yt/client/cache/config.cpp index f0cd2caca5a..232c6d98939 100644 --- a/yt/yt/client/cache/config.cpp +++ b/yt/yt/client/cache/config.cpp @@ -11,7 +11,7 @@ using namespace NApi; void TClientsCacheConfig::Register(TRegistrar registrar) { registrar.Parameter("default_connection", &TThis::DefaultConnection) - .Alias("defailt_config") + .Alias("default_config") .DefaultNew(); registrar.Parameter("per_cluster_connection", &TThis::PerClusterConnection) .Alias("cluster_configs") |