Browse Source

Intermediate changes
commit_hash:f4cb1bdccfb534d71b7f461fc8f8e5656c47bfa5

robot-piglet 1 month ago
parent
commit
6b7c255668

+ 12 - 1
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)
 ----------------------------
 

+ 1 - 1
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"]

+ 7 - 1
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:

+ 30 - 6
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.

+ 1 - 1
contrib/python/fonttools/ya.make

@@ -2,7 +2,7 @@
 
 PY3_LIBRARY()
 
-VERSION(4.55.4)
+VERSION(4.55.6)
 
 LICENSE(MIT)
 

+ 1 - 1
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

+ 22 - 0
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

+ 10 - 2
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)

+ 5 - 0
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"

+ 100 - 1
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

Some files were not shown because too many files changed in this diff