diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 678f620e..f45aa951 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -54,7 +54,7 @@ jobs: needs: functional-tests steps: - name: Login to GitHub Container Registry - uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d + uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -69,7 +69,7 @@ jobs: docker push ghcr.io/repository-service-tuf/repository-service-tuf-api:latest - name: Publish GitHub Release - uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 + uses: softprops/action-gh-release@9d7c94cfd0a1f3ed45544c887983e9fa900f0564 with: name: ${{ github.ref_name }} tag_name: ${{ github.ref }} diff --git a/.github/workflows/publish_container.yml b/.github/workflows/publish_container.yml index 09e6d7b9..3f8f5fb8 100644 --- a/.github/workflows/publish_container.yml +++ b/.github/workflows/publish_container.yml @@ -36,17 +36,17 @@ jobs: uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 + uses: docker/setup-buildx-action@0d103c3126aa41d772a8362f6aa67afac040f80c - name: Login to GitHub Container Registry - uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d + uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push - uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 + uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 with: context: . push: true diff --git a/.github/workflows/publish_docker_dev.yml b/.github/workflows/publish_docker_dev.yml index 5a83da5a..52dbaa53 100644 --- a/.github/workflows/publish_docker_dev.yml +++ b/.github/workflows/publish_docker_dev.yml @@ -29,17 +29,17 @@ jobs: uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 + uses: docker/setup-buildx-action@0d103c3126aa41d772a8362f6aa67afac040f80c - name: Login to GitHub Container Registry - uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d + uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push - uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 + uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 with: context: . push: true diff --git a/.github/workflows/test_docker_build.yml b/.github/workflows/test_docker_build.yml index a658cb38..4dc7d077 100644 --- a/.github/workflows/test_docker_build.yml +++ b/.github/workflows/test_docker_build.yml @@ -18,10 +18,10 @@ jobs: uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 + uses: docker/setup-buildx-action@0d103c3126aa41d772a8362f6aa67afac040f80c - name: Build and push - uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 + uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 with: context: . push: false diff --git a/.github/workflows/update-pre-commit-hooks.yml b/.github/workflows/update-pre-commit-hooks.yml index 59efa046..3fb8689f 100644 --- a/.github/workflows/update-pre-commit-hooks.yml +++ b/.github/workflows/update-pre-commit-hooks.yml @@ -28,7 +28,7 @@ jobs: run: | make tests - name: Create Pull Request - uses: peter-evans/create-pull-request@b1ddad2c994a25fbc81a28b3ec0e368bb2021c50 + uses: peter-evans/create-pull-request@70a41aba780001da0a30141984ae2a0c95d8704e with: token: ${{ secrets.GITHUB_TOKEN }} commit-message: "build: Update pre-commit hooks" diff --git a/.github/workflows/update-python-deps.yml b/.github/workflows/update-python-deps.yml index 893a458a..d1583577 100644 --- a/.github/workflows/update-python-deps.yml +++ b/.github/workflows/update-python-deps.yml @@ -26,7 +26,7 @@ jobs: run: | make tests - name: Create Pull Request - uses: peter-evans/create-pull-request@b1ddad2c994a25fbc81a28b3ec0e368bb2021c50 + uses: peter-evans/create-pull-request@70a41aba780001da0a30141984ae2a0c95d8704e with: token: ${{ secrets.GITHUB_TOKEN }} commit-message: "build: Update Python dependencies" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4c9c7628..3c98ef4a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,12 +20,12 @@ repos: - id: isort args: [-l79, --profile, black, --check, --diff] - repo: https://github.com/psf/black - rev: '24.2.0' + rev: '24.3.0' hooks: - id: black args: [-l79, --check, --diff, .] - repo: https://github.com/PyCQA/bandit - rev: '1.7.7' + rev: '1.7.8' hooks: - id: bandit args: ["-r", "repository_service_tuf_api"] diff --git a/Pipfile.lock b/Pipfile.lock index a111c8cf..a6bbe438 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -45,7 +45,7 @@ "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f", "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028" ], - "markers": "python_version >= '3.7'", + "markers": "python_full_version < '3.11.3'", "version": "==4.0.3" }, "billiard": { @@ -156,11 +156,11 @@ }, "pydantic": { "hashes": [ - "sha256:72c6034df47f46ccdf81869fddb81aade68056003900a8724a4f160700016a2a", - "sha256:e07805c4c7f5c6826e33a1d4c9d47950d7eaf34868e2690f8594d2e30241f11f" + "sha256:b1704e0847db01817624a6b86766967f552dd9dbf3afba4004409f908dcc84e6", + "sha256:cc46fce86607580867bdc3361ad462bab9c222ef042d3da86f2fb333e1d916c5" ], "markers": "python_version >= '3.8'", - "version": "==2.6.3" + "version": "==2.6.4" }, "pydantic-core": { "hashes": [ @@ -266,12 +266,12 @@ }, "redis": { "hashes": [ - "sha256:3f82cc80d350e93042c8e6e7a5d0596e4dd68715babffba79492733e1f367037", - "sha256:4caa8e1fcb6f3c0ef28dba99535101d80934b7d4cd541bbb47f4a3826ee472d1" + "sha256:4973bae7444c0fbed64a06b87446f79361cb7e4ec1538c022d696ed7a5015580", + "sha256:5da9b8fe9e1254293756c16c008e8620b3d15fcc6dde6babde9541850e72a32d" ], "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==5.0.2" + "version": "==5.0.3" }, "six": { "hashes": [ @@ -315,12 +315,12 @@ }, "uvicorn": { "hashes": [ - "sha256:3d9a267296243532db80c83a959a3400502165ade2c1338dea4e67915fd4745a", - "sha256:5c89da2f3895767472a35556e539fd59f7edbe9b1e9c0e1c99eebeadc61838e4" + "sha256:6623abbbe6176204a4226e67607b4d52cc60ff62cda0ff177613645cefa2ece1", + "sha256:cab4473b5d1eaeb5a0f6375ac4bc85007ffc75c3cc1768816d9e5d589857b067" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==0.27.1" + "version": "==0.28.0" }, "vine": { "hashes": [ @@ -373,12 +373,12 @@ }, "bandit": { "hashes": [ - "sha256:17e60786a7ea3c9ec84569fd5aee09936d116cb0cb43151023258340dbffb7ed", - "sha256:527906bec6088cb499aae31bc962864b4e77569e9d529ee51df3a93b4b8ab28a" + "sha256:36de50f720856ab24a24dbaa5fee2c66050ed97c1477e0a1159deab1775eab6b", + "sha256:509f7af645bc0cd8fd4587abc1a038fc795636671ee8204d502b933aee44f381" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==1.7.7" + "version": "==1.7.8" }, "black": { "hashes": [ @@ -616,62 +616,62 @@ "toml" ], "hashes": [ - "sha256:0209a6369ccce576b43bb227dc8322d8ef9e323d089c6f3f26a597b09cb4d2aa", - "sha256:062b0a75d9261e2f9c6d071753f7eef0fc9caf3a2c82d36d76667ba7b6470003", - "sha256:0842571634f39016a6c03e9d4aba502be652a6e4455fadb73cd3a3a49173e38f", - "sha256:16bae383a9cc5abab9bb05c10a3e5a52e0a788325dc9ba8499e821885928968c", - "sha256:18c7320695c949de11a351742ee001849912fd57e62a706d83dfc1581897fa2e", - "sha256:18d90523ce7553dd0b7e23cbb28865db23cddfd683a38fb224115f7826de78d0", - "sha256:1bf25fbca0c8d121a3e92a2a0555c7e5bc981aee5c3fdaf4bb7809f410f696b9", - "sha256:276f6077a5c61447a48d133ed13e759c09e62aff0dc84274a68dc18660104d52", - "sha256:280459f0a03cecbe8800786cdc23067a8fc64c0bd51dc614008d9c36e1659d7e", - "sha256:28ca2098939eabab044ad68850aac8f8db6bf0b29bc7f2887d05889b17346454", - "sha256:2c854ce44e1ee31bda4e318af1dbcfc929026d12c5ed030095ad98197eeeaed0", - "sha256:35eb581efdacf7b7422af677b92170da4ef34500467381e805944a3201df2079", - "sha256:37389611ba54fd6d278fde86eb2c013c8e50232e38f5c68235d09d0a3f8aa352", - "sha256:3b253094dbe1b431d3a4ac2f053b6d7ede2664ac559705a704f621742e034f1f", - "sha256:3b2eccb883368f9e972e216c7b4c7c06cabda925b5f06dde0650281cb7666a30", - "sha256:451f433ad901b3bb00184d83fd83d135fb682d780b38af7944c9faeecb1e0bfe", - "sha256:489763b2d037b164846ebac0cbd368b8a4ca56385c4090807ff9fad817de4113", - "sha256:4af154d617c875b52651dd8dd17a31270c495082f3d55f6128e7629658d63765", - "sha256:506edb1dd49e13a2d4cac6a5173317b82a23c9d6e8df63efb4f0380de0fbccbc", - "sha256:6679060424faa9c11808598504c3ab472de4531c571ab2befa32f4971835788e", - "sha256:69b9f6f66c0af29642e73a520b6fed25ff9fd69a25975ebe6acb297234eda501", - "sha256:6c00cdc8fa4e50e1cc1f941a7f2e3e0f26cb2a1233c9696f26963ff58445bac7", - "sha256:6c0cdedd3500e0511eac1517bf560149764b7d8e65cb800d8bf1c63ebf39edd2", - "sha256:708a3369dcf055c00ddeeaa2b20f0dd1ce664eeabde6623e516c5228b753654f", - "sha256:718187eeb9849fc6cc23e0d9b092bc2348821c5e1a901c9f8975df0bc785bfd4", - "sha256:767b35c3a246bcb55b8044fd3a43b8cd553dd1f9f2c1eeb87a302b1f8daa0524", - "sha256:77fbfc5720cceac9c200054b9fab50cb2a7d79660609200ab83f5db96162d20c", - "sha256:7cbde573904625509a3f37b6fecea974e363460b556a627c60dc2f47e2fffa51", - "sha256:8249b1c7334be8f8c3abcaaa996e1e4927b0e5a23b65f5bf6cfe3180d8ca7840", - "sha256:8580b827d4746d47294c0e0b92854c85a92c2227927433998f0d3320ae8a71b6", - "sha256:8640f1fde5e1b8e3439fe482cdc2b0bb6c329f4bb161927c28d2e8879c6029ee", - "sha256:9a9babb9466fe1da12417a4aed923e90124a534736de6201794a3aea9d98484e", - "sha256:a78ed23b08e8ab524551f52953a8a05d61c3a760781762aac49f8de6eede8c45", - "sha256:abbbd8093c5229c72d4c2926afaee0e6e3140de69d5dcd918b2921f2f0c8baba", - "sha256:ae7f19afe0cce50039e2c782bff379c7e347cba335429678450b8fe81c4ef96d", - "sha256:b3ec74cfef2d985e145baae90d9b1b32f85e1741b04cd967aaf9cfa84c1334f3", - "sha256:b51bfc348925e92a9bd9b2e48dad13431b57011fd1038f08316e6bf1df107d10", - "sha256:b9a4a8dd3dcf4cbd3165737358e4d7dfbd9d59902ad11e3b15eebb6393b0446e", - "sha256:ba3a8aaed13770e970b3df46980cb068d1c24af1a1968b7818b69af8c4347efb", - "sha256:c0524de3ff096e15fcbfe8f056fdb4ea0bf497d584454f344d59fce069d3e6e9", - "sha256:c0a120238dd71c68484f02562f6d446d736adcc6ca0993712289b102705a9a3a", - "sha256:cbbe5e739d45a52f3200a771c6d2c7acf89eb2524890a4a3aa1a7fa0695d2a47", - "sha256:ce8c50520f57ec57aa21a63ea4f325c7b657386b3f02ccaedeccf9ebe27686e1", - "sha256:cf30900aa1ba595312ae41978b95e256e419d8a823af79ce670835409fc02ad3", - "sha256:d25b937a5d9ffa857d41be042b4238dd61db888533b53bc76dc082cb5a15e914", - "sha256:d6cdecaedea1ea9e033d8adf6a0ab11107b49571bbb9737175444cea6eb72328", - "sha256:dec9de46a33cf2dd87a5254af095a409ea3bf952d85ad339751e7de6d962cde6", - "sha256:ebe7c9e67a2d15fa97b77ea6571ce5e1e1f6b0db71d1d5e96f8d2bf134303c1d", - "sha256:ee866acc0861caebb4f2ab79f0b94dbfbdbfadc19f82e6e9c93930f74e11d7a0", - "sha256:f6a09b360d67e589236a44f0c39218a8efba2593b6abdccc300a8862cffc2f94", - "sha256:fcc66e222cf4c719fe7722a403888b1f5e1682d1679bd780e2b26c18bb648cdc", - "sha256:fd6545d97c98a192c5ac995d21c894b581f1fd14cf389be90724d21808b657e2" + "sha256:00838a35b882694afda09f85e469c96367daa3f3f2b097d846a7216993d37f4c", + "sha256:0513b9508b93da4e1716744ef6ebc507aff016ba115ffe8ecff744d1322a7b63", + "sha256:09c3255458533cb76ef55da8cc49ffab9e33f083739c8bd4f58e79fecfe288f7", + "sha256:09ef9199ed6653989ebbcaacc9b62b514bb63ea2f90256e71fea3ed74bd8ff6f", + "sha256:09fa497a8ab37784fbb20ab699c246053ac294d13fc7eb40ec007a5043ec91f8", + "sha256:0f9f50e7ef2a71e2fae92774c99170eb8304e3fdf9c8c3c7ae9bab3e7229c5cf", + "sha256:137eb07173141545e07403cca94ab625cc1cc6bc4c1e97b6e3846270e7e1fea0", + "sha256:1f384c3cc76aeedce208643697fb3e8437604b512255de6d18dae3f27655a384", + "sha256:201bef2eea65e0e9c56343115ba3814e896afe6d36ffd37bab783261db430f76", + "sha256:38dd60d7bf242c4ed5b38e094baf6401faa114fc09e9e6632374388a404f98e7", + "sha256:3b799445b9f7ee8bf299cfaed6f5b226c0037b74886a4e11515e569b36fe310d", + "sha256:3ea79bb50e805cd6ac058dfa3b5c8f6c040cb87fe83de10845857f5535d1db70", + "sha256:40209e141059b9370a2657c9b15607815359ab3ef9918f0196b6fccce8d3230f", + "sha256:41c9c5f3de16b903b610d09650e5e27adbfa7f500302718c9ffd1c12cf9d6818", + "sha256:54eb8d1bf7cacfbf2a3186019bcf01d11c666bd495ed18717162f7eb1e9dd00b", + "sha256:598825b51b81c808cb6f078dcb972f96af96b078faa47af7dfcdf282835baa8d", + "sha256:5fc1de20b2d4a061b3df27ab9b7c7111e9a710f10dc2b84d33a4ab25065994ec", + "sha256:623512f8ba53c422fcfb2ce68362c97945095b864cda94a92edbaf5994201083", + "sha256:690db6517f09336559dc0b5f55342df62370a48f5469fabf502db2c6d1cffcd2", + "sha256:69eb372f7e2ece89f14751fbcbe470295d73ed41ecd37ca36ed2eb47512a6ab9", + "sha256:73bfb9c09951125d06ee473bed216e2c3742f530fc5acc1383883125de76d9cd", + "sha256:742a76a12aa45b44d236815d282b03cfb1de3b4323f3e4ec933acfae08e54ade", + "sha256:7c95949560050d04d46b919301826525597f07b33beba6187d04fa64d47ac82e", + "sha256:8130a2aa2acb8788e0b56938786c33c7c98562697bf9f4c7d6e8e5e3a0501e4a", + "sha256:8a2b2b78c78293782fd3767d53e6474582f62443d0504b1554370bde86cc8227", + "sha256:8ce1415194b4a6bd0cdcc3a1dfbf58b63f910dcb7330fe15bdff542c56949f87", + "sha256:9ca28a302acb19b6af89e90f33ee3e1906961f94b54ea37de6737b7ca9d8827c", + "sha256:a4cdc86d54b5da0df6d3d3a2f0b710949286094c3a6700c21e9015932b81447e", + "sha256:aa5b1c1bfc28384f1f53b69a023d789f72b2e0ab1b3787aae16992a7ca21056c", + "sha256:aadacf9a2f407a4688d700e4ebab33a7e2e408f2ca04dbf4aef17585389eff3e", + "sha256:ae71e7ddb7a413dd60052e90528f2f65270aad4b509563af6d03d53e979feafd", + "sha256:b14706df8b2de49869ae03a5ccbc211f4041750cd4a66f698df89d44f4bd30ec", + "sha256:b1a93009cb80730c9bca5d6d4665494b725b6e8e157c1cb7f2db5b4b122ea562", + "sha256:b2991665420a803495e0b90a79233c1433d6ed77ef282e8e152a324bbbc5e0c8", + "sha256:b2c5edc4ac10a7ef6605a966c58929ec6c1bd0917fb8c15cb3363f65aa40e677", + "sha256:b4d33f418f46362995f1e9d4f3a35a1b6322cb959c31d88ae56b0298e1c22357", + "sha256:b91cbc4b195444e7e258ba27ac33769c41b94967919f10037e6355e998af255c", + "sha256:c74880fc64d4958159fbd537a091d2a585448a8f8508bf248d72112723974cbd", + "sha256:c901df83d097649e257e803be22592aedfd5182f07b3cc87d640bbb9afd50f49", + "sha256:cac99918c7bba15302a2d81f0312c08054a3359eaa1929c7e4b26ebe41e9b286", + "sha256:cc4f1358cb0c78edef3ed237ef2c86056206bb8d9140e73b6b89fbcfcbdd40e1", + "sha256:ccd341521be3d1b3daeb41960ae94a5e87abe2f46f17224ba5d6f2b8398016cf", + "sha256:ce4b94265ca988c3f8e479e741693d143026632672e3ff924f25fab50518dd51", + "sha256:cf271892d13e43bc2b51e6908ec9a6a5094a4df1d8af0bfc360088ee6c684409", + "sha256:d5ae728ff3b5401cc320d792866987e7e7e880e6ebd24433b70a33b643bb0384", + "sha256:d71eec7d83298f1af3326ce0ff1d0ea83c7cb98f72b577097f9083b20bdaf05e", + "sha256:d898fe162d26929b5960e4e138651f7427048e72c853607f2b200909794ed978", + "sha256:d89d7b2974cae412400e88f35d86af72208e1ede1a541954af5d944a8ba46c57", + "sha256:dfa8fe35a0bb90382837b238fff375de15f0dcdb9ae68ff85f7a63649c98527e", + "sha256:e0be5efd5127542ef31f165de269f77560d6cdef525fffa446de6f7e9186cfb2", + "sha256:fdfafb32984684eb03c2d83e1e51f64f0906b11e64482df3c5db936ce3839d48", + "sha256:ff7687ca3d7028d8a5f0ebae95a6e4827c5616b31a4ee1192bdfde697db110d4" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==7.4.3" + "version": "==7.4.4" }, "cryptography": { "hashes": [ @@ -989,11 +989,11 @@ }, "packaging": { "hashes": [ - "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", - "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7" + "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5", + "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9" ], "markers": "python_version >= '3.7'", - "version": "==23.2" + "version": "==24.0" }, "pathspec": { "hashes": [ @@ -1085,12 +1085,12 @@ }, "pytest": { "hashes": [ - "sha256:d4051d623a2e0b7e51960ba963193b09ce6daeb9759a451844a21e4ddedfc1bd", - "sha256:edfaaef32ce5172d5466b5127b42e0d6d35ebbe4453f0e3505d96afd93f6b096" + "sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7", + "sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==8.0.2" + "version": "==8.1.1" }, "pytest-cov": { "hashes": [ @@ -1298,11 +1298,11 @@ }, "setuptools": { "hashes": [ - "sha256:02fa291a0471b3a18b2b2481ed902af520c69e8ae0919c13da936542754b4c56", - "sha256:5c0806c7d9af348e6dd3777b4f4dbb42c7ad85b190104837488eab9a7c945cf8" + "sha256:0ff4183f8f42cd8fa3acea16c45205521a4ef28f73c6391d8a25e92893134f2e", + "sha256:c21c49fb1042386df081cb5d86759792ab89efca84cf114889191cd09aacc80c" ], "markers": "python_version >= '3.8'", - "version": "==69.1.1" + "version": "==69.2.0" }, "six": { "hashes": [ @@ -1407,10 +1407,10 @@ }, "sphinxcontrib-plantuml": { "hashes": [ - "sha256:ce9362ad3d4bbcc4c30dfdfc308aacad2c07b344635316681a711269b8e00bea" + "sha256:97a4f2a26af91db88770ccf8a3b2e03305bcda7ec41a7f969fc8cb27b84a3c44" ], "index": "pypi", - "version": "==0.28" + "version": "==0.29" }, "sphinxcontrib-qthelp": { "hashes": [ @@ -1448,12 +1448,12 @@ }, "tox": { "hashes": [ - "sha256:286807f8a581e55785dc30ff956a81583016625f6dd296948aa7a43001c1bb00", - "sha256:35976739e39e60e913bef4cf8226be64eb9eed6fdb9a44c6924265ebe11cbcae" + "sha256:b03754b6ee6dadc70f2611da82b4ed8f625fcafd247e15d1d0cb056f90a06d3b", + "sha256:f0ad758c3bbf7e237059c929d3595479363c3cdd5a06ac3e49d1dd020ffbee45" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==4.14.0" + "version": "==4.14.1" }, "typing-extensions": { "hashes": [ diff --git a/docs/swagger.json b/docs/swagger.json index 866c0393..7226efb6 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -3,7 +3,7 @@ "info": { "title": "Repository Service for TUF API", "description": "Repository Service for TUF Rest API", - "version": "0.12.0b1" + "version": "0.13.0b1" }, "paths": { "/api/v1/bootstrap/": { @@ -734,6 +734,29 @@ "message": "Bootstrap accepted." } }, + "DelegatedRole": { + "properties": { + "expiration": { + "type": "integer", + "exclusiveMinimum": 0.0, + "title": "Expiration" + }, + "path_patterns": { + "items": { + "type": "string" + }, + "type": "array", + "minItems": 1, + "title": "Path Patterns" + } + }, + "type": "object", + "required": [ + "expiration", + "path_patterns" + ], + "title": "DelegatedRole" + }, "DeleteData": { "properties": { "task_id": { @@ -1356,6 +1379,20 @@ "type": "null" } ] + }, + "delegated_roles": { + "anyOf": [ + { + "additionalProperties": { + "$ref": "#/components/schemas/DelegatedRole" + }, + "type": "object" + }, + { + "type": "null" + } + ], + "title": "Delegated Roles" } }, "type": "object", diff --git a/repository_service_tuf_api/__version__.py b/repository_service_tuf_api/__version__.py index cdcb9bf5..b8ae21ff 100644 --- a/repository_service_tuf_api/__version__.py +++ b/repository_service_tuf_api/__version__.py @@ -5,6 +5,6 @@ # # SPDX-License-Identifier: MIT -version = "0.12.0b1" +version = "0.13.0b1" copyright = "Copyright (c) 2024 Repository Service for TUF Contributors" author = "Kairo de Araujo" diff --git a/repository_service_tuf_api/bootstrap.py b/repository_service_tuf_api/bootstrap.py index c91c0147..e8fd5074 100644 --- a/repository_service_tuf_api/bootstrap.py +++ b/repository_service_tuf_api/bootstrap.py @@ -5,13 +5,14 @@ import json import logging +import re import time from datetime import datetime from threading import Thread -from typing import Dict, Literal, Optional +from typing import Any, Dict, List, Optional from fastapi import HTTPException, status -from pydantic import BaseModel, ConfigDict, Field +from pydantic import BaseModel, ConfigDict, Field, model_validator from repository_service_tuf_api import ( bootstrap_state, @@ -22,10 +23,13 @@ ) from repository_service_tuf_api.common_models import ( BaseErrorResponse, - Roles, TUFMetadata, ) +# Pattern of allowed names to be used by custom target delegated roles +DELEGATED_NAMES_PATTERN = "[a-zA-Z0-9_-]+" + + with open("tests/data_examples/bootstrap/payload_bins.json") as f: content = f.read() payload_example = json.loads(content) @@ -39,12 +43,61 @@ class BinsRole(Role): number_of_delegated_bins: int = Field(gt=1, lt=16385) +class DelegatedRole(Role): + # Note: No validation is required for path_patterns as these patterns are + # only used to distribute artifacts. No files are created based on them. + path_patterns: List[str] = Field(min_length=1) + + @model_validator(mode="before") + @classmethod + def validate_path_patterns(cls, values: Dict[str, Any]): + path_patterns = values.get("path_patterns") + if any(len(pattern) < 1 for pattern in path_patterns): + raise ValueError("No empty strings are allowed as path patterns") + + return values + + class RolesData(BaseModel): root: Role targets: Role snapshot: Role timestamp: Role bins: Optional[BinsRole] = Field(default=None) + delegated_roles: Optional[Dict[str, DelegatedRole]] = Field(default=None) + + @model_validator(mode="before") + @classmethod + def validate_delegations(cls, values: Dict[str, Any]) -> Dict[str, Any]: + bins = values.get("bins") + delegated_roles = values.get("delegated_roles") + if (bins is None and delegated_roles is None) or ( + bins is not None and delegated_roles is not None + ): + err_msg = "Exactly one of 'bins' and 'delegated_roles' must be set" + raise ValueError(err_msg) + + return values + + @model_validator(mode="before") + @classmethod + def validate_delegated_roles_names( + cls, values: Dict[str, Any] + ) -> Dict[str, Any]: + # Validation of custom target delegated names is required as otherwise + # an attacker can use a custom target name to point to a specific place + # in a file system and override a file or cause unexpected behavior. + delegated_roles = values.get("delegated_roles") + if delegated_roles is not None: + # The keys of the delegated_roles dict are the names of the roles. + for role_name in delegated_roles.keys(): + if re.fullmatch(DELEGATED_NAMES_PATTERN, role_name) is None: + raise ValueError( + f"Delegated custom target name {role_name} not allowed" + " Only a-z, A-Z, 0-9, - and _ characters can be used" + ) + + return values class Settings(BaseModel): @@ -54,7 +107,7 @@ class Settings(BaseModel): class BootstrapPayload(BaseModel): model_config = ConfigDict(json_schema_extra={"example": payload_example}) settings: Settings - metadata: Dict[Literal[Roles.ROOT.value], TUFMetadata] + metadata: Dict[str, TUFMetadata] timeout: int | None = Field(default=300, description="Timeout in seconds") diff --git a/repository_service_tuf_api/common_models.py b/repository_service_tuf_api/common_models.py index 214722e6..6cec8104 100644 --- a/repository_service_tuf_api/common_models.py +++ b/repository_service_tuf_api/common_models.py @@ -20,10 +20,6 @@ class Roles(Enum): def values() -> List[str]: return Literal["root", "targets", "snapshot", "timestamp", "bins"] - @staticmethod - def online_roles_values() -> List[str]: - return Literal["targets", "snapshot", "timestamp", "bins"] - class BaseErrorResponse(BaseModel): error: str = Field(description="Error message") diff --git a/repository_service_tuf_api/config.py b/repository_service_tuf_api/config.py index 43761fa8..219312b4 100644 --- a/repository_service_tuf_api/config.py +++ b/repository_service_tuf_api/config.py @@ -16,15 +16,6 @@ repository_metadata, settings_repository, ) -from repository_service_tuf_api.common_models import Roles - -with open("tests/data_examples/config/settings.json") as f: - content = f.read() -example_settings = json.loads(content) - -with open("tests/data_examples/config/update_settings.json") as f: - content = f.read() -example_update_settings = json.loads(content) class PutData(BaseModel): @@ -51,13 +42,18 @@ class PutResponse(BaseModel): class Settings(BaseModel): # Only online roles can be dict keys - expiration: Dict[Roles.online_roles_values(), int] + expiration: Dict[str, int] + + +with open("tests/data_examples/config/update_settings.json") as f: + content = f.read() +put_payload = json.loads(content) class PutPayload(BaseModel): model_config = ConfigDict( json_schema_extra={ - "example": example_update_settings, + "example": put_payload, } ) @@ -95,6 +91,11 @@ def put(payload: PutPayload): return PutResponse(data=data, message="Settings successfully submitted.") +with open("tests/data_examples/config/settings.json") as f: + content = f.read() +example_settings = json.loads(content) + + class GetResponse(BaseModel): model_config = ConfigDict( json_schema_extra={ @@ -108,7 +109,7 @@ class GetResponse(BaseModel): message: str -def get(): +def get() -> GetResponse: bs_state = bootstrap_state() if bs_state.bootstrap is False: raise HTTPException( diff --git a/repository_service_tuf_api/targets.py b/repository_service_tuf_api/targets.py index 84479dec..290e4314 100644 --- a/repository_service_tuf_api/targets.py +++ b/repository_service_tuf_api/targets.py @@ -16,10 +16,6 @@ repository_metadata, ) -with open("tests/data_examples/targets/payload.json") as f: - content = f.read() -add_payload = json.loads(content) - class ResponseData(BaseModel): targets: List[str] @@ -65,6 +61,11 @@ class Targets(BaseModel): path: str +with open("tests/data_examples/targets/payload.json") as f: + content = f.read() +add_payload = json.loads(content) + + class AddPayload(BaseModel): """ POST method required Payload. diff --git a/requirements-dev.txt b/requirements-dev.txt index c981eab9..657d2c6c 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -3,7 +3,7 @@ alabaster==0.7.16; python_version >= '3.9' anyio==4.3.0; python_version >= '3.8' attrs==23.2.0; python_version >= '3.7' babel==2.14.0; python_version >= '3.7' -bandit==1.7.7; python_version >= '3.8' +bandit==1.7.8; python_version >= '3.8' black==24.2.0; python_version >= '3.8' cachetools==5.3.3; python_version >= '3.7' certifi==2024.2.2; python_version >= '3.6' @@ -13,7 +13,7 @@ chardet==5.2.0; python_version >= '3.7' charset-normalizer==3.3.2; python_full_version >= '3.7.0' click==8.1.7; python_version >= '3.7' colorama==0.4.6; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6' -coverage[toml]==7.4.3; python_version >= '3.8' +coverage[toml]==7.4.4; python_version >= '3.8' cryptography==42.0.5; python_version >= '3.7' distlib==0.3.8 docutils==0.20.1; python_version >= '3.7' @@ -42,7 +42,7 @@ mistune==0.8.4 mypy-extensions==1.0.0; python_version >= '3.5' myst-parser==2.0.0; python_version >= '3.8' nodeenv==1.8.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6' -packaging==23.2; python_version >= '3.7' +packaging==24.0; python_version >= '3.7' pathspec==0.12.1; python_version >= '3.8' pbr==6.0.0; python_version >= '2.6' platformdirs==4.2.0; python_version >= '3.8' @@ -54,7 +54,7 @@ pycparser==2.21 pyflakes==3.2.0; python_version >= '3.8' pygments==2.17.2; python_version >= '3.7' pyproject-api==1.6.1; python_version >= '3.8' -pytest==8.0.2; python_version >= '3.8' +pytest==8.1.1; python_version >= '3.8' pytest-cov==4.1.0; python_version >= '3.7' pytest-xdist==3.5.0; python_version >= '3.7' pyyaml==6.0.1; python_version >= '3.6' @@ -62,7 +62,7 @@ referencing==0.33.0; python_version >= '3.8' requests==2.31.0; python_version >= '3.7' rich==13.7.1; python_full_version >= '3.7.0' rpds-py==0.18.0; python_version >= '3.8' -setuptools==69.1.1; python_version >= '3.8' +setuptools==69.2.0; python_version >= '3.8' six==1.16.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' sniffio==1.3.1; python_version >= '3.7' snowballstemmer==2.2.0 @@ -75,19 +75,19 @@ sphinxcontrib-httpdomain==1.8.1; python_version >= '2.7' and python_version not sphinxcontrib-jquery==4.1; python_version >= '2.7' sphinxcontrib-jsmath==1.0.1; python_version >= '3.5' sphinxcontrib-openapi==0.7.0; python_version >= '3.6' -sphinxcontrib-plantuml==0.28 +sphinxcontrib-plantuml==0.29 sphinxcontrib-qthelp==1.0.7; python_version >= '3.9' sphinxcontrib-serializinghtml==1.1.10; python_version >= '3.9' stevedore==5.2.0; python_version >= '3.8' tomli==2.0.1; python_version < '3.11' -tox==4.14.0; python_version >= '3.8' +tox==4.14.1; python_version >= '3.8' typing-extensions==4.10.0; python_version >= '3.8' urllib3==2.2.1; python_version >= '3.8' virtualenv==20.25.1; python_version >= '3.7' voluptuous==0.14.2; python_version >= '3.8' amqp==5.2.0; python_version >= '3.6' annotated-types==0.6.0; python_version >= '3.8' -async-timeout==4.0.3; python_version >= '3.7' +async-timeout==4.0.3; python_full_version < '3.11.3' billiard==4.2.0; python_version >= '3.7' celery==5.3.6; python_version >= '3.8' click-didyoumean==0.3.0; python_full_version >= '3.6.2' and python_full_version < '4.0.0' @@ -97,13 +97,13 @@ dynaconf==3.2.4; python_version >= '3.8' fastapi==0.110.0; python_version >= '3.8' kombu==5.3.5; python_version >= '3.8' prompt-toolkit==3.0.43; python_full_version >= '3.7.0' -pydantic==2.6.3; python_version >= '3.8' +pydantic==2.6.4; python_version >= '3.8' pydantic-core==2.16.3; python_version >= '3.8' python-dateutil==2.9.0.post0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' python-multipart==0.0.9; python_version >= '3.8' -redis==5.0.2; python_version >= '3.7' +redis==5.0.3; python_version >= '3.7' starlette==0.36.3; python_version >= '3.8' tzdata==2024.1; python_version >= '2' -uvicorn==0.27.1; python_version >= '3.8' +uvicorn==0.28.0; python_version >= '3.8' vine==5.1.0; python_version >= '3.6' wcwidth==0.2.13 diff --git a/requirements.txt b/requirements.txt index c829db58..763abc89 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ amqp==5.2.0; python_version >= '3.6' annotated-types==0.6.0; python_version >= '3.8' anyio==4.3.0; python_version >= '3.8' -async-timeout==4.0.3; python_version >= '3.7' +async-timeout==4.0.3; python_full_version < '3.11.3' billiard==4.2.0; python_version >= '3.7' celery==5.3.6; python_version >= '3.8' click==8.1.7; python_version >= '3.7' @@ -16,16 +16,16 @@ h11==0.14.0; python_version >= '3.7' idna==3.6; python_version >= '3.5' kombu==5.3.5; python_version >= '3.8' prompt-toolkit==3.0.43; python_full_version >= '3.7.0' -pydantic==2.6.3; python_version >= '3.8' +pydantic==2.6.4; python_version >= '3.8' pydantic-core==2.16.3; python_version >= '3.8' python-dateutil==2.9.0.post0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' python-multipart==0.0.9; python_version >= '3.8' -redis==5.0.2; python_version >= '3.7' +redis==5.0.3; python_version >= '3.7' six==1.16.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' sniffio==1.3.1; python_version >= '3.7' starlette==0.36.3; python_version >= '3.8' typing-extensions==4.10.0; python_version >= '3.8' tzdata==2024.1; python_version >= '2' -uvicorn==0.27.1; python_version >= '3.8' +uvicorn==0.28.0; python_version >= '3.8' vine==5.1.0; python_version >= '3.6' wcwidth==0.2.13 diff --git a/tests/data_examples/bootstrap/payload_custom_targets.json b/tests/data_examples/bootstrap/payload_custom_targets.json new file mode 100644 index 00000000..19c03f67 --- /dev/null +++ b/tests/data_examples/bootstrap/payload_custom_targets.json @@ -0,0 +1,99 @@ +{ + "settings": { + "roles": { + "root": { + "expiration": 365 + }, + "targets": { + "expiration": 365 + }, + "snapshot": { + "expiration": 1 + }, + "timestamp": { + "expiration": 1 + }, + "delegated_roles": { + "foo": { + "expiration": 30, + "path_patterns": ["project/f"] + }, + "bar": { + "expiration": 60, + "path_patterns": ["project/b"] + } + } + } + }, + "metadata": { + "root": { + "signatures": [ + { + "keyid": "1cebe343e35f0213f6136758e6c3a8f8e1f9eeb7e47a07d5cb336462ed31dcb7", + "sig": "b3c81b6579442b8672ff460ac36cdb51064109fcfa93f0e72dabbf338f7d0c24e1a91d3696d72d81f60e94b2782ab1bf41e6b8f3e535538216f4379bf30a4302" + }, + { + "keyid": "800dfb5a1982b82b7893e58035e19f414f553fc08cbb1130cfbae302a7b7fee5", + "sig": "fdfbb1877da404504e8b9c111d87680df5291094dafa01f66bfe6031e470d7c63cc477c1e79210a89e27734758e54fb54adc529918b1211d9dd9971c8bb26c0a" + } + ], + "signed": { + "_type": "root", + "version": 1, + "spec_version": "1.0.30", + "expires": "2024-02-17T10:55:28Z", + "consistent_snapshot": true, + "keys": { + "f7a6872f297634219a80141caa2ec9ae8802098b07b67963272603e36cc19fd8": { + "keytype": "ed25519", + "scheme": "ed25519", + "keyval": { + "public": "9fe7ddccb75b977a041424a1fdc142e01be4abab918dc4c611fbfe4a3360a9a8" + } + }, + "1cebe343e35f0213f6136758e6c3a8f8e1f9eeb7e47a07d5cb336462ed31dcb7": { + "keytype": "ed25519", + "scheme": "ed25519", + "keyval": { + "public": "ad1709b3cb419b99c5cd7427d6411522e5a93aec6767453e91af921a73d22a3c" + } + }, + "800dfb5a1982b82b7893e58035e19f414f553fc08cbb1130cfbae302a7b7fee5": { + "keytype": "ed25519", + "scheme": "ed25519", + "keyval": { + "public": "7098f769f6ab8502b50f3b58686b8a042d5d3bb75d8b3a48a2fcbc15a0223501" + } + } + }, + "roles": { + "snapshot": { + "keyids": [ + "f7a6872f297634219a80141caa2ec9ae8802098b07b67963272603e36cc19fd8" + ], + "threshold": 1 + }, + "timestamp": { + "keyids": [ + "f7a6872f297634219a80141caa2ec9ae8802098b07b67963272603e36cc19fd8" + ], + "threshold": 1 + }, + "targets": { + "keyids": [ + "f7a6872f297634219a80141caa2ec9ae8802098b07b67963272603e36cc19fd8" + ], + "threshold": 1 + }, + "root": { + "keyids": [ + "1cebe343e35f0213f6136758e6c3a8f8e1f9eeb7e47a07d5cb336462ed31dcb7", + "800dfb5a1982b82b7893e58035e19f414f553fc08cbb1130cfbae302a7b7fee5" + ], + "threshold": 1 + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/data_examples/config/update_settings_custom_targets.json b/tests/data_examples/config/update_settings_custom_targets.json new file mode 100644 index 00000000..43540297 --- /dev/null +++ b/tests/data_examples/config/update_settings_custom_targets.json @@ -0,0 +1,11 @@ +{ + "settings": { + "expiration": { + "targets": 100, + "snapshot": 50, + "timestamp": 20, + "foo": 1, + "bar": 300 + } + } +} \ No newline at end of file diff --git a/tests/unit/api/test_bootstrap.py b/tests/unit/api/test_bootstrap.py index ee1d93ce..2bd263ad 100644 --- a/tests/unit/api/test_bootstrap.py +++ b/tests/unit/api/test_bootstrap.py @@ -7,6 +7,7 @@ import json import pretend +import pytest from fastapi import status BOOTSTRAP_URL = "/api/v1/bootstrap/" @@ -155,6 +156,65 @@ def test_post_bootstrap_bins_delegation(self, test_client, monkeypatch): pretend.call(task_id="123", timeout=300) ] + def test_post_bootstrap_custom_delegation(self, test_client, monkeypatch): + mocked_bootstrap_state = pretend.call_recorder( + lambda *a: pretend.stub( + bootstrap=False, state="finished", task_id="task_id" + ) + ) + monkeypatch.setattr( + "repository_service_tuf_api.bootstrap.bootstrap_state", + mocked_bootstrap_state, + ) + mocked_async_result = pretend.stub(state="SUCCESS") + mocked_repository_metadata = pretend.stub( + apply_async=pretend.call_recorder(lambda *a, **kw: None), + AsyncResult=pretend.call_recorder(lambda *a: mocked_async_result), + ) + monkeypatch.setattr( + "repository_service_tuf_api.bootstrap.repository_metadata", + mocked_repository_metadata, + ) + monkeypatch.setattr( + "repository_service_tuf_api.bootstrap.get_task_id", lambda: "123" + ) + monkeypatch.setattr( + "repository_service_tuf_api.bootstrap.pre_lock_bootstrap", + lambda *a: None, + ) + mocked__check_bootstrap_status = pretend.call_recorder(lambda *a: None) + monkeypatch.setattr( + "repository_service_tuf_api.bootstrap._check_bootstrap_status", + mocked__check_bootstrap_status, + ) + + fake_time = datetime.datetime(2019, 6, 16, 9, 5, 1) + fake_datetime = pretend.stub( + now=pretend.call_recorder(lambda: fake_time) + ) + monkeypatch.setattr( + "repository_service_tuf_api.bootstrap.datetime", fake_datetime + ) + + path = "tests/data_examples/bootstrap/payload_custom_targets.json" + with open(path) as f: + f_data = f.read() + payload = json.loads(f_data) + + response = test_client.post(BOOTSTRAP_URL, json=payload) + + assert fake_datetime.now.calls == [pretend.call()] + assert response.status_code == status.HTTP_202_ACCEPTED + assert response.url == f"{test_client.base_url}{BOOTSTRAP_URL}" + assert response.json() == { + "message": "Bootstrap accepted.", + "data": {"task_id": "123", "last_update": "2019-06-16T09:05:01"}, + } + assert mocked_bootstrap_state.calls == [pretend.call()] + assert mocked__check_bootstrap_status.calls == [ + pretend.call(task_id="123", timeout=300) + ] + def test_post_bootstrap_unrecognized_field(self, test_client, monkeypatch): mocked_bootstrap_state = pretend.call_recorder( lambda *a: pretend.stub( @@ -458,3 +518,84 @@ def test_post_bootstrap_empty_payload(self, test_client): }, ] } + + def test_post_payload_no_bins_or_delegated_targets( + self, test_client, monkeypatch + ): + mocked_bootstrap_state = pretend.call_recorder( + lambda *a: pretend.stub( + bootstrap=False, state="finished", task_id="task_id" + ) + ) + monkeypatch.setattr( + "repository_service_tuf_api.bootstrap.bootstrap_state", + mocked_bootstrap_state, + ) + with open("tests/data_examples/bootstrap/payload_bins.json") as f: + f_data = f.read() + + payload = json.loads(f_data) + del payload["settings"]["roles"]["bins"] + response = test_client.post(BOOTSTRAP_URL, json=payload) + assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY + assert response.url == f"{test_client.base_url}{BOOTSTRAP_URL}" + err_msg = "Exactly one of 'bins' and 'delegated_roles' must be set" + assert err_msg in response.text + + @pytest.mark.parametrize("name", ["bad*", "|bad", ".bad", "/", "\\"]) + def test_post_payload_bad_delegated_role_names( + self, test_client, monkeypatch, name + ): + mocked_bootstrap_state = pretend.call_recorder( + lambda *a: pretend.stub( + bootstrap=False, state="finished", task_id="task_id" + ) + ) + monkeypatch.setattr( + "repository_service_tuf_api.bootstrap.bootstrap_state", + mocked_bootstrap_state, + ) + path = "tests/data_examples/bootstrap/payload_custom_targets.json" + with open(path) as f: + f_data = f.read() + + payload = json.loads(f_data) + payload["settings"]["roles"]["delegated_roles"] = { + name: {"expiration": 30, "path_prefixes": ["project/f"]}, + } + response = test_client.post(BOOTSTRAP_URL, json=payload) + assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY + assert response.url == f"{test_client.base_url}{BOOTSTRAP_URL}" + err_msg_1 = "Delegated custom target name" + err_msg_2 = "not allowed" + err_msg_3 = " Only a-z, A-Z, 0-9, - and _ characters can be used" + assert err_msg_1 in response.text + assert err_msg_2 in response.text + assert err_msg_3 in response.text + + def test_post_payload_delegated_role_with_empty_path_pattern( + self, test_client, monkeypatch + ): + mocked_bootstrap_state = pretend.call_recorder( + lambda *a: pretend.stub( + bootstrap=False, state="finished", task_id="task_id" + ) + ) + monkeypatch.setattr( + "repository_service_tuf_api.bootstrap.bootstrap_state", + mocked_bootstrap_state, + ) + path = "tests/data_examples/bootstrap/payload_custom_targets.json" + with open(path) as f: + f_data = f.read() + + payload = json.loads(f_data) + payload["settings"]["roles"]["delegated_roles"]["foo"] = { + "expiration": 30, + "path_patterns": [""], + } + response = test_client.post(BOOTSTRAP_URL, json=payload) + assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY + assert response.url == f"{test_client.base_url}{BOOTSTRAP_URL}" + err_msg = "No empty strings are allowed as path patterns" + assert err_msg in response.text diff --git a/tests/unit/api/test_config.py b/tests/unit/api/test_config.py index 828e753d..eb1fb935 100644 --- a/tests/unit/api/test_config.py +++ b/tests/unit/api/test_config.py @@ -65,6 +65,59 @@ def test_put_settings(self, test_client, monkeypatch): ) ] + def test_put_settings_with_custom_targets(self, test_client, monkeypatch): + path = "tests/data_examples/config/update_settings_custom_targets.json" + with open(path) as f: + f_data = f.read() + + payload = json.loads(f_data) + + mocked_bootstrap_state = pretend.call_recorder( + lambda *a: pretend.stub(bootstrap=True) + ) + monkeypatch.setattr( + "repository_service_tuf_api.config.bootstrap_state", + mocked_bootstrap_state, + ) + mocked_get_task_id = pretend.call_recorder(lambda: "task-id") + monkeypatch.setattr( + "repository_service_tuf_api.config.get_task_id", mocked_get_task_id + ) + mocked_repository_metadata = pretend.stub( + apply_async=pretend.call_recorder(lambda **kw: None) + ) + monkeypatch.setattr( + "repository_service_tuf_api.config.repository_metadata", + mocked_repository_metadata, + ) + fake_time = datetime.datetime(2019, 6, 16, 9, 5, 1) + fake_datetime = pretend.stub( + now=pretend.call_recorder(lambda: fake_time) + ) + monkeypatch.setattr( + "repository_service_tuf_api.config.datetime", fake_datetime + ) + response = test_client.put(URL, json=payload) + assert fake_datetime.now.calls == [pretend.call()] + assert response.status_code == status.HTTP_202_ACCEPTED + assert response.json() == { + "data": { + "task_id": "task-id", + "last_update": "2019-06-16T09:05:01", + }, + "message": "Settings successfully submitted.", + } + assert mocked_bootstrap_state.calls == [pretend.call()] + assert mocked_get_task_id.calls == [pretend.call()] + assert mocked_repository_metadata.apply_async.calls == [ + pretend.call( + kwargs={"action": "update_settings", "payload": payload}, + task_id="task-id", + queue="metadata_repository", + acks_late=True, + ) + ] + def test_put_settings_without_bootstrap(self, test_client, monkeypatch): with open("tests/data_examples/config/update_settings.json") as f: f_data = f.read()