diff --git a/core/commands/repository/interactors/encode_secret_string.py b/core/commands/repository/interactors/encode_secret_string.py index d7f1b2bde..6eae647e1 100644 --- a/core/commands/repository/interactors/encode_secret_string.py +++ b/core/commands/repository/interactors/encode_secret_string.py @@ -10,12 +10,20 @@ class EncodeSecretStringInteractor(BaseInteractor): @sync_to_async - def execute(self, owner: Owner, repo: Repository, value: str) -> str: + def execute(self, owner: Owner, repo_name: str, value: str) -> str: if not self.current_user.is_authenticated: raise Unauthenticated() + + author = Owner.objects.filter( + username=owner.username, service=self.service + ).first() + repo = ( + Repository.objects.viewable_repos(self.current_owner) + .filter(author=author, name=repo_name) + .first() + ) if not repo: raise ValidationError("Repo not found") - to_encode = "/".join( ( owner.service, diff --git a/core/commands/repository/interactors/tests/test_encode_secret_string.py b/core/commands/repository/interactors/tests/test_encode_secret_string.py index 97c02c492..23c4732b8 100644 --- a/core/commands/repository/interactors/tests/test_encode_secret_string.py +++ b/core/commands/repository/interactors/tests/test_encode_secret_string.py @@ -14,21 +14,23 @@ class EncodeSecretStringInteractorTest(TransactionTestCase): @async_to_sync - def execute(self, owner, repo, value): - return EncodeSecretStringInteractor(owner, "github").execute(owner, repo, value) + def execute(self, owner, repo_name, value): + return EncodeSecretStringInteractor(owner, "github").execute( + owner, repo_name, value + ) def test_encode_secret_string(self): owner = OwnerFactory() - repo = RepositoryFactory(author=owner, name="repo-1") - res = self.execute(owner, repo=repo, value="token-1") + RepositoryFactory(author=owner, name="repo-1") + res = self.execute(owner, repo_name="repo-1", value="token-1") check_encryptor = yaml_secret_encryptor assert "token-1" in check_encryptor.decode(res[7:]) def test_validation_error_when_repo_not_found(self): owner = OwnerFactory() with pytest.raises(ValidationError): - self.execute(owner, repo=None, value="token-1") + self.execute(owner, repo_name=None, value="token-1") def test_user_is_not_authenticated(self): - with pytest.raises(Unauthenticated) as e: - self.execute(None, repo=None, value="test") + with pytest.raises(Unauthenticated): + self.execute(None, repo_name=None, value="test") diff --git a/core/commands/repository/repository.py b/core/commands/repository/repository.py index 0d8d76691..8a2bb18b2 100644 --- a/core/commands/repository/repository.py +++ b/core/commands/repository/repository.py @@ -68,7 +68,7 @@ def activate_measurements( def erase_repository(self, repo_name: str, owner: Owner): return self.get_interactor(EraseRepositoryInteractor).execute(repo_name, owner) - def encode_secret_string(self, owner: Owner, repo: Repository, value: str): + def encode_secret_string(self, owner: Owner, repo_name: str, value: str): return self.get_interactor(EncodeSecretStringInteractor).execute( - owner, repo, value + owner, repo_name, value ) diff --git a/graphql_api/tests/mutation/test_encode_secret_string.py b/graphql_api/tests/mutation/test_encode_secret_string.py new file mode 100644 index 000000000..c0812e13b --- /dev/null +++ b/graphql_api/tests/mutation/test_encode_secret_string.py @@ -0,0 +1,44 @@ +from django.test import TransactionTestCase +from shared.encryption.yaml_secret import yaml_secret_encryptor + +from codecov_auth.tests.factories import OwnerFactory +from core.tests.factories import RepositoryFactory +from graphql_api.tests.helper import GraphQLTestHelper + +query = """ +mutation($input: EncodeSecretStringInput!) { + encodeSecretString(input: $input) { + value + error { + __typename + ... on ResolverError { + message + } + } + } +} +""" + + +class TestEncodeSecretString(TransactionTestCase, GraphQLTestHelper): + def _request(self): + data = self.gql_request( + query, + owner=self.org, + variables={"input": {"repoName": "test-repo", "value": "token-1"}}, + ) + return data["encodeSecretString"]["value"] + + def setUp(self): + self.org = OwnerFactory(username="test-org") + self.repo = RepositoryFactory( + name="test-repo", + author=self.org, + private=True, + ) + self.owner = OwnerFactory(permission=[self.repo.pk]) + + def test_encoded_secret_string(self): + res = self._request() + check_encryptor = yaml_secret_encryptor + assert "token-1" in check_encryptor.decode(res[7:]) diff --git a/graphql_api/tests/test_repository_encoded_secret_string.py b/graphql_api/tests/test_repository_encoded_secret_string.py deleted file mode 100644 index fd3180433..000000000 --- a/graphql_api/tests/test_repository_encoded_secret_string.py +++ /dev/null @@ -1,40 +0,0 @@ -from django.test import TransactionTestCase -from shared.encryption.yaml_secret import yaml_secret_encryptor - -from codecov_auth.tests.factories import OwnerFactory -from core.tests.factories import RepositoryFactory - -from .helper import GraphQLTestHelper - - -class TestEncodedString(TransactionTestCase, GraphQLTestHelper): - def _request(self, variables=None): - query = f""" - query EncodedSecretString($value: String!) {{ - owner(username: "{self.org.username}") {{ - repository(name: "{self.repo.name}") {{ - ... on Repository {{ - encodedSecretString(value: $value) {{ - value - }} - }} - }} - }} - }} - """ - data = self.gql_request(query, owner=self.owner, variables=variables) - return data["owner"]["repository"]["encodedSecretString"]["value"] - - def setUp(self): - self.org = OwnerFactory(username="test-org") - self.repo = RepositoryFactory( - name="test-repo", - author=self.org, - private=True, - ) - self.owner = OwnerFactory(permission=[self.repo.pk]) - - def test_encoded_secret_string(self): - res = self._request(variables={"value": "token-1"}) - check_encryptor = yaml_secret_encryptor - assert "token-1" in check_encryptor.decode(res[7:]) diff --git a/graphql_api/types/inputs/encode_secret_string.graphql b/graphql_api/types/inputs/encode_secret_string.graphql new file mode 100644 index 000000000..2ea2e0910 --- /dev/null +++ b/graphql_api/types/inputs/encode_secret_string.graphql @@ -0,0 +1,4 @@ +input EncodeSecretStringInput { + repoName: String! + value: String! +} \ No newline at end of file diff --git a/graphql_api/types/mutation/__init__.py b/graphql_api/types/mutation/__init__.py index d82090309..ff320f21c 100644 --- a/graphql_api/types/mutation/__init__.py +++ b/graphql_api/types/mutation/__init__.py @@ -7,6 +7,7 @@ from .delete_component_measurements import gql_delete_component_measurements from .delete_flag import gql_delete_flag from .delete_session import gql_delete_session +from .encode_secret_string import gql_encode_secret_string from .erase_repository import gql_erase_repository from .mutation import mutation_resolvers from .onboard_user import gql_onboard_user @@ -48,4 +49,5 @@ mutation = mutation + gql_update_repository mutation = mutation + gql_update_self_hosted_settings mutation = mutation + gql_regenerate_repository_upload_token +mutation = mutation + gql_encode_secret_string mutation = mutation + gql_store_event_metrics diff --git a/graphql_api/types/mutation/encode_secret_string/__init__.py b/graphql_api/types/mutation/encode_secret_string/__init__.py new file mode 100644 index 000000000..562e17b07 --- /dev/null +++ b/graphql_api/types/mutation/encode_secret_string/__init__.py @@ -0,0 +1,10 @@ +from graphql_api.helpers.ariadne import ariadne_load_local_graphql + +from .encode_secret_string import ( + error_encode_secret_string, + resolve_encode_secret_string, +) + +gql_encode_secret_string = ariadne_load_local_graphql( + __file__, "encode_secret_string.graphql" +) diff --git a/graphql_api/types/mutation/encode_secret_string/encode_secret_string.graphql b/graphql_api/types/mutation/encode_secret_string/encode_secret_string.graphql new file mode 100644 index 000000000..03662a716 --- /dev/null +++ b/graphql_api/types/mutation/encode_secret_string/encode_secret_string.graphql @@ -0,0 +1,6 @@ +union EncodeSecretStringError = ValidationError | UnauthenticatedError + +type EncodeSecretStringPayload { + error: EncodeSecretStringError + value: String +} \ No newline at end of file diff --git a/graphql_api/types/mutation/encode_secret_string/encode_secret_string.py b/graphql_api/types/mutation/encode_secret_string/encode_secret_string.py new file mode 100644 index 000000000..672ddeb0f --- /dev/null +++ b/graphql_api/types/mutation/encode_secret_string/encode_secret_string.py @@ -0,0 +1,24 @@ +from ariadne import UnionType + +from graphql_api.helpers.mutation import ( + require_authenticated, + resolve_union_error_type, + wrap_error_handling_mutation, +) + + +@wrap_error_handling_mutation +@require_authenticated +async def resolve_encode_secret_string(_, info, input) -> None: + command = info.context["executor"].get_command("repository") + repo_name = input.get("repoName") + value = input.get("value") + current_owner = info.context["request"].current_owner + value = command.encode_secret_string( + repo_name=repo_name, owner=current_owner, value=value + ) + return {"value": value} + + +error_encode_secret_string = UnionType("EraseRepositoryError") +error_encode_secret_string.type_resolver(resolve_union_error_type) diff --git a/graphql_api/types/mutation/mutation.graphql b/graphql_api/types/mutation/mutation.graphql index cbf03c2b2..33dcc9c57 100644 --- a/graphql_api/types/mutation/mutation.graphql +++ b/graphql_api/types/mutation/mutation.graphql @@ -29,5 +29,6 @@ type Mutation { updateRepository(input: UpdateRepositoryInput!): UpdateRepositoryPayload updateSelfHostedSettings(input: UpdateSelfHostedSettingsInput!): UpdateSelfHostedSettingsPayload regenerateRepositoryUploadToken(input: RegenerateRepositoryUploadTokenInput!): RegenerateRepositoryUploadTokenPayload + encodeSecretString(input: EncodeSecretStringInput!): EncodeSecretStringPayload storeEventMetric(input: StoreEventMetricsInput!): StoreEventMetricsPayload } diff --git a/graphql_api/types/mutation/mutation.py b/graphql_api/types/mutation/mutation.py index 98503764b..638c85024 100644 --- a/graphql_api/types/mutation/mutation.py +++ b/graphql_api/types/mutation/mutation.py @@ -13,6 +13,10 @@ ) from .delete_flag import error_delete_flag, resolve_delete_flag from .delete_session import error_delete_session, resolve_delete_session +from .encode_secret_string import ( + error_encode_secret_string, + resolve_encode_secret_string, +) from .erase_repository import error_erase_repository, resolve_erase_repository from .onboard_user import error_onboard_user, resolve_onboard_user from .regenerate_org_upload_token import ( @@ -84,6 +88,8 @@ mutation_bindable.field("regenerateRepositoryUploadToken")( resolve_regenerate_repository_upload_token ) +mutation_bindable.field("encodeSecretString")(resolve_encode_secret_string) + mutation_bindable.field("storeEventMetric")(resolve_store_event_metrics) mutation_resolvers = [ @@ -110,5 +116,6 @@ error_update_repository, error_update_self_hosted_settings, error_regenerate_repository_upload_token, + error_encode_secret_string, error_store_event_metrics, ] diff --git a/graphql_api/types/repository/repository.graphql b/graphql_api/types/repository/repository.graphql index 608991835..f34294e86 100644 --- a/graphql_api/types/repository/repository.graphql +++ b/graphql_api/types/repository/repository.graphql @@ -81,7 +81,6 @@ type Repository { orderingDirection: OrderingDirection ): [ComponentMeasurements!]! componentsYaml(termId: String): [ComponentsYaml]! - encodedSecretString(value: String!): EncodedSecretString! testAnalyticsEnabled: Boolean } @@ -118,8 +117,4 @@ type BranchEdge { node: Branch! } -type EncodedSecretString { - value: String! -} - union RepositoryResult = Repository | NotFoundError | OwnerNotActivatedError diff --git a/graphql_api/types/repository/repository.py b/graphql_api/types/repository/repository.py index 5b81aa093..59268d5b0 100644 --- a/graphql_api/types/repository/repository.py +++ b/graphql_api/types/repository/repository.py @@ -536,13 +536,3 @@ def resolve_is_first_pull_request(repository: Repository, info) -> bool: return not first_pr.compared_to return False - - -@repository_bindable.field("encodedSecretString") -@sync_to_async -def resolve_encoded_secret_string( - repository: Repository, info: GraphQLResolveInfo, value: str -) -> dict[str, str]: - command = info.context["executor"].get_command("repository") - owner = info.context["request"].current_owner - return {"value": command.encode_secret_string(owner, repository, value)}