Skip to content

Commit

Permalink
Add support for certificate type DEVELOPER_ID_APPLICATION_G2 (#415)
Browse files Browse the repository at this point in the history
  • Loading branch information
priitlatt authored Jul 5, 2024
1 parent 283ce7e commit 279c900
Show file tree
Hide file tree
Showing 8 changed files with 242 additions and 67 deletions.
10 changes: 5 additions & 5 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
rev: v4.6.0
hooks:
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/asottile/add-trailing-comma
rev: v2.5.1
rev: v3.1.0
hooks:
- id: add-trailing-comma
- repo: https://github.com/psf/black
rev: 23.3.0
rev: 24.4.2
hooks:
- id: black
language_version: python3.11
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.0.275
rev: v0.5.0
hooks:
- id: ruff
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.4.1
rev: v1.10.1
hooks:
- id: mypy
additional_dependencies: [types-requests]
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
Version 0.53.3
-------------

This release contains changes from [PR #415](https://github.com/codemagic-ci-cd/cli-tools/pull/415).

**Bugfixes**
- Support signing certificates with type `DEVELOPER_ID_APPLICATION_G2` for `app-store-connect`.

**Development**
- Update `ruff` settings to be compatible with latest version.
- Update `pre-commit` hook versions.

Version 0.53.2
-------------

Expand Down
14 changes: 8 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "codemagic-cli-tools"
version = "0.53.2"
version = "0.53.3"
description = "CLI tools used in Codemagic builds"
readme = "README.md"
authors = [
Expand Down Expand Up @@ -65,6 +65,11 @@ build-backend = "poetry.core.masonry.api"
line-length = 120

[tool.ruff]
line-length = 120
target-version = "py37"
exclude = [".venv", "stubs"]

[tool.ruff.lint]
select = [
"F", # https://beta.ruff.rs/docs/rules/#pyflakes-f
"E", # https://beta.ruff.rs/docs/rules/#error-e
Expand All @@ -75,19 +80,16 @@ select = [
"ISC", # https://beta.ruff.rs/docs/rules/#flake8-implicit-str-concat-isc
"ASYNC", # https://beta.ruff.rs/docs/rules/#flake8-async-async
]
line-length = 120
target-version = "py37"
exclude = [".venv", "stubs"]

[tool.ruff.per-file-ignores]
[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["F401"]
"doc.py" = ["E402"]
"src/codemagic/apple/resources/*.py" = ["N815"]
"src/codemagic/google/resources/*.py" = ["N815"]
"src/codemagic/google_play/resources/*.py" = ["N815"]
"src/codemagic/models/export_options.py" = ["N815"]

[tool.ruff.isort]
[tool.ruff.lint.isort]
force-single-line = true

[tool.mypy]
Expand Down
2 changes: 1 addition & 1 deletion src/codemagic/__version__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
__title__ = "codemagic-cli-tools"
__description__ = "CLI tools used in Codemagic builds"
__version__ = "0.53.2.dev"
__version__ = "0.53.3.dev"
__url__ = "https://github.com/codemagic-ci-cd/cli-tools"
__licence__ = "GNU General Public License v3.0"
52 changes: 52 additions & 0 deletions src/codemagic/apple/resources/enums.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
from __future__ import annotations

import re
from collections import OrderedDict
from typing import List
from typing import Optional
from typing import Sequence
from typing import Tuple
from typing import Union

from codemagic.models.enums import ResourceEnum
from codemagic.utilities.decorators import deprecated
Expand Down Expand Up @@ -157,7 +162,13 @@ def get_default_display_name(cls, capability_type_value: str) -> str:


class CertificateType(ResourceEnum):
"""
https://developer.apple.com/documentation/appstoreconnectapi/certificatetype
"""

DEVELOPER_ID_APPLICATION = "DEVELOPER_ID_APPLICATION"
# Undocumented developer ID certificate with profile type "G2 Sub-CA"
DEVELOPER_ID_APPLICATION_G2 = "DEVELOPER_ID_APPLICATION_G2"
DEVELOPER_ID_KEXT = "DEVELOPER_ID_KEXT"
DEVELOPMENT = "DEVELOPMENT"
DISTRIBUTION = "DISTRIBUTION"
Expand Down Expand Up @@ -200,6 +211,47 @@ def from_profile_type(cls, profile_type: ProfileType) -> CertificateType:
else:
raise ValueError(f"Certificate type for profile type {profile_type} is unknown")

@classmethod
def resolve_applicable_types(
cls,
certificate_types: Optional[Union[CertificateType, Sequence[CertificateType]]] = None,
profile_type: Optional[ProfileType] = None,
) -> List[CertificateType]:
"""
Construct a list of unique certificate types based on the provided certificate and
provisioning profile types. Resolved types are ordered so that
- provided `certificate_types` come first in original ordering (if given),
- which is followed by `profile_type` primary accompanying certificate type and
finally `profile_type`'s secondary matching certificate type in case it exists.
"""

types: List[CertificateType] = []

if isinstance(certificate_types, CertificateType):
types.append(certificate_types)
elif certificate_types:
types.extend(certificate_types)

if profile_type:
types.append(CertificateType.from_profile_type(profile_type))
# Include iOS and Mac App distribution certificate types backwards compatibility.
# In the past iOS and Mac App Store profiles used to map to iOS and Mac App distribution
# certificates, and consequently they too can be used with those profiles.
if profile_type is ProfileType.IOS_APP_STORE or profile_type is ProfileType.IOS_APP_ADHOC:
types.append(CertificateType.IOS_DISTRIBUTION)
elif profile_type is ProfileType.MAC_APP_STORE:
types.append(CertificateType.MAC_APP_DISTRIBUTION)

# Developer ID profiles can also be used with undocumented (as of 04.07.24) flavor
# of developer ID application certificates that have special G2 suffix in the type name.
# Said profiles themselves have the same type as before regardless of whether they
# are of type "G2 Sub-GA" or "Previous Sub-GA".
if profile_type in (ProfileType.MAC_APP_DIRECT, ProfileType.MAC_CATALYST_APP_DIRECT):
types.append(CertificateType.DEVELOPER_ID_APPLICATION_G2)

# Remove duplicate entries from the list in order-preserving way.
return list(OrderedDict.fromkeys(types))


class ContentRightsDeclaration(ResourceEnum):
DOES_NOT_USE_THIRD_PARTY_CONTENT = "DOES_NOT_USE_THIRD_PARTY_CONTENT"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from typing import List
from typing import Optional
from typing import Sequence
from typing import Set
from typing import Union
from typing import cast

Expand Down Expand Up @@ -166,10 +165,23 @@ def list_certificates(
raise AppStoreConnectError("Cannot create or save resource without certificate private key")

_certificate_type: Optional[CertificateType] = _deprecated_kwargs.get("certificate_type")
certificate_types_filter = self._resolve_certificate_types(_certificate_type, certificate_types, profile_type)
if isinstance(_certificate_type, CertificateType):
warning = (
"Deprecation warning! Keyword argument "
'"certificate_type: Optional[CertificateType]" is deprecated in favor of '
'"certificate_types: Optional[Union[CertificateType, Sequence[CertificateType]]]", '
"and is subject for removal."
)
self.logger.warning(Colors.RED(warning))
certificate_types = _certificate_type

certificate_types_filter = CertificateType.resolve_applicable_types(
certificate_types=certificate_types,
profile_type=profile_type,
)

certificate_filter = self.api_client.signing_certificates.Filter(
certificate_type=certificate_types_filter,
certificate_type=certificate_types_filter if certificate_types_filter else None,
display_name=display_name,
)
certificates = self._list_resources(
Expand All @@ -196,42 +208,3 @@ def list_certificates(
)

return certificates

def _resolve_certificate_types(
self,
certificate_type: Optional[CertificateType],
certificate_types: Optional[Union[CertificateType, Sequence[CertificateType]]],
profile_type: Optional[ProfileType],
) -> Optional[List[CertificateType]]:
types: Set[CertificateType] = set()

if isinstance(certificate_types, CertificateType):
types.add(certificate_types)
elif certificate_types is not None:
types.update(certificate_types)

if isinstance(certificate_type, CertificateType):
warning = (
"Deprecation warning! Keyword argument "
'"certificate_type: Optional[CertificateType]" is deprecated in favor of '
'"certificate_types: Optional[Union[CertificateType, Sequence[CertificateType]]] = None", '
"and is subject for removal."
)
self.logger.warning(Colors.RED(warning))
types.add(certificate_type)

if profile_type:
types.add(CertificateType.from_profile_type(profile_type))
# Include iOS and Mac App distribution certificate types backwards compatibility.
# In the past iOS and Mac App Store profiles used to map to iOS and Mac App distribution
# certificates, and consequently they too can be used with those profiles.
if profile_type is ProfileType.IOS_APP_STORE:
types.add(CertificateType.IOS_DISTRIBUTION)
elif profile_type is ProfileType.IOS_APP_ADHOC:
types.add(CertificateType.IOS_DISTRIBUTION)
elif profile_type is ProfileType.MAC_APP_STORE:
types.add(CertificateType.MAC_APP_DISTRIBUTION)
elif profile_type is ProfileType.MAC_APP_DIRECT:
types.add(CertificateType.DEVELOPER_ID_APPLICATION)

return list(types) if types else None
Original file line number Diff line number Diff line change
Expand Up @@ -121,18 +121,7 @@ def _get_or_create_certificates(
certificate_key_password: Optional[Types.CertificateKeyPasswordArgument],
create_resource: bool,
) -> List[SigningCertificate]:
certificate_types = [CertificateType.from_profile_type(profile_type)]
# Include iOS and Mac App distribution certificate types backwards compatibility.
# In the past iOS and Mac App Store profiles used to map to iOS and Mac App distribution
# certificates, and we want to keep using existing certificates for as long as possible.
if profile_type is ProfileType.IOS_APP_STORE:
certificate_types.append(CertificateType.IOS_DISTRIBUTION)
elif profile_type is ProfileType.IOS_APP_ADHOC:
certificate_types.append(CertificateType.IOS_DISTRIBUTION)
elif profile_type is ProfileType.MAC_APP_STORE:
certificate_types.append(CertificateType.MAC_APP_DISTRIBUTION)
elif profile_type is ProfileType.MAC_APP_DIRECT:
certificate_types.append(CertificateType.DEVELOPER_ID_APPLICATION)
certificate_types = CertificateType.resolve_applicable_types(profile_type=profile_type)

certificates = self.list_certificates(
certificate_types=certificate_types,
Expand Down Expand Up @@ -218,7 +207,7 @@ def _create_missing_profiles(
platform: Optional[BundleIdPlatform] = None,
) -> Iterator[Profile]:
if not bundle_ids_without_profiles:
return []
return
if platform is None:
platform = bundle_ids_without_profiles[0].attributes.platform

Expand Down
Loading

0 comments on commit 279c900

Please sign in to comment.