ewjoachim / pypitoken

Creation & manipulation of PyPI tokens
http://pypitoken.readthedocs.io/en/latest/
MIT License
12 stars 1 forks source link

Invalid API Token: token is expired #157

Closed fschulze closed 1 year ago

fschulze commented 1 year ago

Since the update to 6.x the date restriction seems to be broken. I already upgraded to the new API. Here is what I'm doing:

            token = pypitoken.Token.load(password)
            if now is None:
                now = int(time.time())
            token.restrict(
                project_names=[project],
                not_before=now - 1,
                not_after=now + 60)
            self.info(
                "Used 'pypitoken' to create a unique %s token "
                "valid for 60 seconds for upload to the %r project." % (
                    title, project))
            self.debug("token info:", now, token.restrictions)
            return token.dump()

I get the following info for the token:

Used 'pypitoken' to create a unique PyPI token valid for 60 seconds for upload to the 'devpi-server' project.
[debug] token info: 1674556978 [LegacyNoopRestriction(), DateRestriction(not_before=1674556977, not_after=1674557038), ProjectNamesRestriction(project_names=['devpi-server'])]

The token is used immediately for the upload and I get 403 Invalid API Token: token is expired from test.pypi.org (and pypi.org).

I also wonder where the LegacyNoopRestriction comes from, didn't dig into the code for that yet.

Right after loading the token I get this from token._macaroon.inspect(): 'location test.pypi.org\nidentifier b\'[identifier redacted]\'\ncid {"permissions": "user", "version": 1}\nsignature [signature redacted]'

Afterwards: 'location test.pypi.org\nidentifier b\'[identifier redacted]\'\ncid {"permissions": "user", "version": 1}\ncid [0, 1674556977, 1674557038]\ncid [1, ["devpi-server"]]\nsignature [signature redacted]'

fschulze commented 1 year ago

I've now investigated where LegacyNoopRestriction comes from and it is there when a legacy token from PyPI is used. I created a new token on test.pypi.org and there I get UserIDRestriction instead. Even with the new token I still get the same error. With the old token and using legacy restrictions everything works as expected:

            token = pypitoken.Token.load(password)
            legacy = any(
                isinstance(x, pypitoken.LegacyNoopRestriction)
                for x in token.restrictions)
            if now is None:
                now = int(time.time())
            if legacy:
                token.restrict(
                    legacy_project_names=[project],
                    legacy_not_before=now - 1,
                    legacy_not_after=now + 60)
            else:
                token.restrict(
                    project_names=[project],
                    not_before=now - 1,
                    not_after=now + 60)
            self.info(
                "Used 'pypitoken' to create a unique %s token "
                "valid for 60 seconds for upload to the %r project." % (
                    title, project))
            self.debug("token info:", now, token.restrictions)
            return token.dump()
ewjoachim commented 1 year ago

I guess I really need to add end to end tests to the repo, that upload a real package to testpypi, then.

I was afraid that maybe pypi code wouldn't be able to handle a token having both old and new-style restrictions, but the fact new tokens fail the same seems like a proof that it's not that. Maybe I really got the format wrong, but it worked when I tried. I'm going to have to explore this further. Probably not before next week.

(Ideally we'd need to compare the restriction the lib creates with restrictions created by PyPI itself, so it might involve setting up a PyPI dev-env)

ewjoachim commented 1 year ago

Found it. In warehouse, date restrictions use format [0, not_after, not_before] whereas in pypitoken, we currently erroneously use [0, not_before, not_after].

ewjoachim commented 1 year ago

https://pypi.org/project/pypitoken/6.0.3/

fschulze commented 1 year ago

Thanks, it works now (with test.pypi.org at least)!