diff --git a/python/tk-core/_core_upgrader.py b/python/tk-core/_core_upgrader.py index f3959670..3fdcb201 100644 --- a/python/tk-core/_core_upgrader.py +++ b/python/tk-core/_core_upgrader.py @@ -19,11 +19,11 @@ """ -import os -import sys -import stat import datetime +import os import shutil +import stat +import sys LooseVersion = None try: @@ -99,7 +99,7 @@ def __current_version_less_than(log, sgtk_install_root, ver): """ log.debug("Checking if the currently installed version is less than %s..." % ver) - if __is_upgrade(sgtk_install_root) == False: + if not __is_upgrade(sgtk_install_root): # there is no current version. So it is definitely # not at least version X log.debug( diff --git a/python/tk-core/codecov.yml b/python/tk-core/codecov.yml index 5fe4673e..cf2cef2f 100644 --- a/python/tk-core/codecov.yml +++ b/python/tk-core/codecov.yml @@ -1,4 +1,21 @@ +coverage: + status: + project: + default: + # Coverage checks are advisory only - never block CI + informational: true + # Only flag if overall project coverage drops below 80% + target: 80% + # Ignore drops of 1% or less (noise from small changes) + threshold: 1% + patch: + default: + # Coverage checks are advisory only - never block CI + informational: true + # Only flag if less than 80% of new/changed lines are covered + target: 80% + ignore: # ignore generated QT files - "**tank/authentication/ui/login_dialog.py" @@ -6,3 +23,11 @@ ignore: # disable PySide6 code coverage for now - "**tank/util/pyside6_patcher.py" + + # flowam and other files not covered by unit tests + - "**tank/bootstrap/manager.py" + - "**tank/flowam/create.py" + - "**tank/flowam/host.py" + - "**tank/flowam/open.py" + - "**tank/flowam/utils.py" + - "**tank/platform/engine.py" diff --git a/python/tk-core/commit_id b/python/tk-core/commit_id index 321245c5..3f254928 100644 --- a/python/tk-core/commit_id +++ b/python/tk-core/commit_id @@ -1 +1 @@ -bc5f5e526d45baae95e2233f625550a31ccec92d +2fd64cd3f4f3f7f363c51ddf40f031605087107d diff --git a/python/tk-core/developer/README.md b/python/tk-core/developer/README.md index 27bd1f16..d1992e52 100644 --- a/python/tk-core/developer/README.md +++ b/python/tk-core/developer/README.md @@ -27,6 +27,14 @@ The `requirements/update_python_packages.py` script automates the creation and m - Generate the `frozen_requirements.txt` file for consistency. 3. Validate that the `pkgs.zip` file contains all necessary packages and matches the updated requirements. +### Shared (Python-version-independent) vendor zips + +In addition to the per-version `pkgs.zip`, `requirements/any/` holds pure-Python +packages that are safe to load across every supported Python version (e.g. the +Autodesk Flow Data Beta SDK). Each zip is auto-discovered by +`tank_vendor/__init__.py` and contains the importable package plus its +`.dist-info/` directory at the zip's root. + ## How to upgrade ruamel.yaml Until version `0.10.10`, the contents of the library was located at `tank_vendor/ruamel_yaml`. diff --git a/python/tk-core/developer/bake_config.py b/python/tk-core/developer/bake_config.py index 6c16ecfd..9375ce77 100644 --- a/python/tk-core/developer/bake_config.py +++ b/python/tk-core/developer/bake_config.py @@ -15,8 +15,8 @@ # system imports import os -import sys import shutil +import sys # add sgtk API this_folder = os.path.abspath(os.path.dirname(__file__)) @@ -25,23 +25,27 @@ # any installed version in site-packages sys.path.insert(0, python_folder) +import functools + # sgtk imports from tank import LogManager -from tank.util import filesystem -from tank.descriptor import Descriptor, descriptor_uri_to_dict -from tank.descriptor import create_descriptor, is_descriptor_version_missing -from tank.descriptor.errors import TankDescriptorError from tank.bootstrap import constants as bootstrap_constants -import functools - +from tank.descriptor import ( + Descriptor, + create_descriptor, + descriptor_uri_to_dict, + is_descriptor_version_missing, +) +from tank.descriptor.errors import TankDescriptorError +from tank.util import filesystem from utils import ( - cache_apps, - authenticate, - add_authentication_options, OptionParserLineBreakingEpilog, + add_authentication_options, + authenticate, + automated_setup_documentation, + cache_apps, cleanup_bundle_cache, wipe_folder, - automated_setup_documentation, ) # Set up logging diff --git a/python/tk-core/developer/build_plugin.py b/python/tk-core/developer/build_plugin.py index fcf7abd1..c42be482 100644 --- a/python/tk-core/developer/build_plugin.py +++ b/python/tk-core/developer/build_plugin.py @@ -16,11 +16,12 @@ a primed bundle cache. """ +import datetime + # system imports import os -import sys import shutil -import datetime +import sys # add sgtk API this_folder = os.path.abspath(os.path.dirname(__file__)) @@ -31,22 +32,27 @@ # sgtk imports from tank import LogManager -from tank.util import filesystem, sgre as re -from tank.errors import TankError -from tank.descriptor import Descriptor, descriptor_uri_to_dict, descriptor_dict_to_uri -from tank.descriptor import create_descriptor, is_descriptor_version_missing -from tank.bootstrap.baked_configuration import BakedConfiguration from tank.bootstrap import constants as bootstrap_constants +from tank.bootstrap.baked_configuration import BakedConfiguration +from tank.descriptor import ( + Descriptor, + create_descriptor, + descriptor_dict_to_uri, + descriptor_uri_to_dict, + is_descriptor_version_missing, +) +from tank.errors import TankError +from tank.util import filesystem +from tank.util import sgre as re from tank_vendor import yaml - from utils import ( - cache_apps, - authenticate, - add_authentication_options, OptionParserLineBreakingEpilog, + add_authentication_options, + authenticate, + automated_setup_documentation, + cache_apps, cleanup_bundle_cache, wipe_folder, - automated_setup_documentation, ) # set up logging diff --git a/python/tk-core/developer/find_flow_project.py b/python/tk-core/developer/find_flow_project.py new file mode 100644 index 00000000..804ed05f --- /dev/null +++ b/python/tk-core/developer/find_flow_project.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python3 +# Copyright (c) 2026 Shotgun Software Inc. +# CONFIDENTIAL AND PROPRIETARY +""" +Standalone CLI to find your Flow project ID using the Flow Data SDK. + +Use this script to retrieve the Flow project ID for a given FPT project, +so it can be linked in the FPT site settings. + +Defaults to production values. Override any individual setting via flags. + +Usage: + # Production (default): + python scripts/find_flow_project.py + + # Override just the endpoint (e.g. for staging): + python scripts/find_flow_project.py --endpoint + + # Override multiple values: + python scripts/find_flow_project.py --endpoint --application-id --auth-base-url + + # Filter by project name (case-insensitive): + python scripts/find_flow_project.py --name "My Project" +""" + +from __future__ import annotations + +import argparse +import os +import sys + +# Set up sys.path so tk-core python packages are importable +_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +_REPO_ROOT = os.path.dirname(_SCRIPT_DIR) +sys.path.insert(0, os.path.join(_REPO_ROOT, "python")) + +from tank.authentication.flow_auth._constants import ( + DEFAULT_AUTH_APPLICATION_ID, + DEFAULT_AUTH_BASE_URL, + DEFAULT_AUTH_CALLBACK_URL, + REQUIRED_SCOPES, +) +from tank.util import LocalFileStorageManager +from tank_vendor.adsk_auth import AuthConfig, get_access_token +from tank_vendor.flow_data_sdk import GQLClient +from tank_vendor.flow_data_sdk.base import model as flow_model +from tank_vendor.flow_data_sdk.base.client import AuthenticationHandlerBase + +DEFAULT_ENDPOINT = "https://medm-v2.medata-p-ue1.cloudos.autodesk.com/api/v2/graphql" + + +class TokenAuthHandler(AuthenticationHandlerBase): + """Auth handler that returns a pre-fetched token.""" + + def __init__(self, token: str) -> None: + self._token = token + + def get_authentication_token(self) -> str: + return self._token + + +def main() -> None: + parser = argparse.ArgumentParser( + description="List Flow collections/projects to find project IDs for FPT linking.", + ) + parser.add_argument( + "--endpoint", + default=DEFAULT_ENDPOINT, + help=f"Flow GraphQL endpoint URL (default: {DEFAULT_ENDPOINT})", + ) + parser.add_argument( + "--application-id", + default=DEFAULT_AUTH_APPLICATION_ID, + help="APS application ID (default: prod value)", + ) + parser.add_argument( + "--auth-base-url", + default=DEFAULT_AUTH_BASE_URL, + help=f"APS auth base URL (default: {DEFAULT_AUTH_BASE_URL})", + ) + parser.add_argument( + "--auth-callback-url", + default=DEFAULT_AUTH_CALLBACK_URL, + help=f"OAuth callback URL (default: {DEFAULT_AUTH_CALLBACK_URL})", + ) + parser.add_argument( + "--name", + help="Only show projects whose name contains this string (case-insensitive)", + ) + parser.add_argument( + "--force-reauth", + action="store_true", + help="Ignore cached tokens and force browser login", + ) + parser.add_argument( + "--time-out", + type=float, + default=120.0, + help="Browser auth timeout in seconds (default: 120)", + ) + args = parser.parse_args() + + print(f"Endpoint : {args.endpoint}") + print("Authenticating... (browser may open if no valid cached token)") + + auth_config = AuthConfig( + application_id=args.application_id, + base_url=args.auth_base_url, + callback_url=args.auth_callback_url, + description="Flow find project CLI", + required_application_scopes=REQUIRED_SCOPES, + storage_dir=LocalFileStorageManager.get_global_root( + LocalFileStorageManager.CACHE + ), + ) + token = get_access_token( + auth_config, + force_reauthentication=args.force_reauth, + time_out=args.time_out, + ) + + client = GQLClient( + endpoint=args.endpoint, + auth_handler=TokenAuthHandler(token), + ) + + name_filter = (args.name or "").strip().lower() + collections_resp = client.service_collection.collections( + flow_model.CollectionsInput() + ).call() + + collections = getattr(collections_resp, "collections", None) or [] + if not collections: + print("No collections found for your account.") + sys.exit(0) + + found_any = False + for col in collections: + col_id = getattr(col, "id", None) or "" + col_name = getattr(col, "name", None) or "(no name)" + projects_resp = client.service_project.projects_by_collection_id( + flow_model.ProjectsByCollectionIdInput(collection_id=col_id) + ).call() + projects = getattr(projects_resp, "projects", None) or [] + for proj in projects: + proj_name = getattr(proj, "name", None) or "(no name)" + proj_id = getattr(proj, "id", None) or "" + if name_filter and name_filter not in proj_name.lower(): + continue + if not found_any: + print("\nCollection -> Project") + print("-" * 60) + found_any = True + print(f" {col_name} -> {proj_name}") + print(f" Project ID: {proj_id}") + print() + if not projects and not name_filter: + print(f" {col_name} -> (no projects)") + + if not found_any and name_filter: + print(f"No project matching '{args.name}' found.") + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/python/tk-core/developer/populate_bundle_cache.py b/python/tk-core/developer/populate_bundle_cache.py index 6ac83b6d..30062379 100644 --- a/python/tk-core/developer/populate_bundle_cache.py +++ b/python/tk-core/developer/populate_bundle_cache.py @@ -18,7 +18,6 @@ # system imports import os - import sys # add sgtk API @@ -30,16 +29,15 @@ # sgtk imports from sgtk import LogManager -from sgtk.util import filesystem from sgtk.descriptor import Descriptor, create_descriptor, is_descriptor_version_missing - +from sgtk.util import filesystem from utils import ( - cache_apps, - authenticate, - add_authentication_options, OptionParserLineBreakingEpilog, - cleanup_bundle_cache, + add_authentication_options, + authenticate, automated_setup_documentation, + cache_apps, + cleanup_bundle_cache, ) # set up logging diff --git a/python/tk-core/developer/utils/__init__.py b/python/tk-core/developer/utils/__init__.py index 15eeef6d..9d6c3285 100644 --- a/python/tk-core/developer/utils/__init__.py +++ b/python/tk-core/developer/utils/__init__.py @@ -1,7 +1,7 @@ -from .caching import cache_apps, cleanup_bundle_cache, wipe_folder # noqa -from .option_parser import OptionParserLineBreakingEpilog # noqa -from .authentication import ( +from .authentication import ( # noqa add_authentication_options, authenticate, automated_setup_documentation, -) # noqa +) +from .caching import cache_apps, cleanup_bundle_cache, wipe_folder # noqa +from .option_parser import OptionParserLineBreakingEpilog # noqa diff --git a/python/tk-core/developer/utils/caching.py b/python/tk-core/developer/utils/caching.py index da8ae723..17ca8a92 100644 --- a/python/tk-core/developer/utils/caching.py +++ b/python/tk-core/developer/utils/caching.py @@ -8,16 +8,15 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -import os import glob +import os import shutil import stat -from tank.util import filesystem -from tank.platform import environment -from tank.descriptor import Descriptor, create_descriptor - from tank import LogManager +from tank.descriptor import Descriptor, create_descriptor +from tank.platform import environment +from tank.util import filesystem logger = LogManager.get_logger("utils.caching") diff --git a/python/tk-core/hooks/cache_location.py b/python/tk-core/hooks/cache_location.py index 1578a9d7..988dfebb 100644 --- a/python/tk-core/hooks/cache_location.py +++ b/python/tk-core/hooks/cache_location.py @@ -12,9 +12,10 @@ Hook to control path cache and bundle cache folder creation. """ -import sgtk import os -from sgtk.util import filesystem, LocalFileStorageManager + +import sgtk +from sgtk.util import LocalFileStorageManager, filesystem HookBaseClass = sgtk.get_hook_baseclass() log = sgtk.LogManager.get_logger(__name__) diff --git a/python/tk-core/hooks/ensure_folder_exists.py b/python/tk-core/hooks/ensure_folder_exists.py index 1b8573e3..ce56404a 100644 --- a/python/tk-core/hooks/ensure_folder_exists.py +++ b/python/tk-core/hooks/ensure_folder_exists.py @@ -13,8 +13,8 @@ :class:`~sgtk.platform.Application.ensure_folder_exists` method is called. """ -from sgtk.util import filesystem from sgtk import Hook +from sgtk.util import filesystem class EnsureFolderExists(Hook): diff --git a/python/tk-core/hooks/get_current_login.py b/python/tk-core/hooks/get_current_login.py index 78870b5a..1197e9f7 100644 --- a/python/tk-core/hooks/get_current_login.py +++ b/python/tk-core/hooks/get_current_login.py @@ -27,8 +27,9 @@ current user. """ -from tank import Hook import os + +from tank import Hook from tank.util import is_windows @@ -54,5 +55,5 @@ def execute(self, **kwargs): pwd_entry = pwd.getpwuid(os.geteuid()) return pwd_entry[0] - except: + except Exception: return None diff --git a/python/tk-core/hooks/process_folder_creation.py b/python/tk-core/hooks/process_folder_creation.py index f6c0a83b..d0f18ddc 100644 --- a/python/tk-core/hooks/process_folder_creation.py +++ b/python/tk-core/hooks/process_folder_creation.py @@ -13,9 +13,10 @@ called. """ -from tank import Hook import os import shutil + +from tank import Hook from tank.util import is_windows diff --git a/python/tk-core/hooks/process_folder_name.py b/python/tk-core/hooks/process_folder_name.py index b9d4e654..cc38edc6 100644 --- a/python/tk-core/hooks/process_folder_name.py +++ b/python/tk-core/hooks/process_folder_name.py @@ -24,9 +24,10 @@ raise TankError("Shot names cannot start with AA!") """ -from tank import Hook import re +from tank import Hook + class ProcessFolderName(Hook): def execute(self, entity_type, entity_id, field_name, value, **kwargs): diff --git a/python/tk-core/info.yml b/python/tk-core/info.yml index 42d7d155..e62d7ec9 100644 --- a/python/tk-core/info.yml +++ b/python/tk-core/info.yml @@ -12,9 +12,10 @@ # This is automatically replaced with appropriate values # as the core bundle is uploaded to the app store. -version: "v0.23.8" +version: "v0.23.10" documentation_url: "https://help.autodesk.com/view/SGDEV/ENU/?contextId=SA_INTEGRATIONS_USER_GUIDE" requires_shotgun_version: "v5.0.0" +minimum_python_version: "3.9" features: # lean_config history diff --git a/python/tk-core/python/sgtk/__init__.py b/python/tk-core/python/sgtk/__init__.py index 0df56282..0b1f2684 100644 --- a/python/tk-core/python/sgtk/__init__.py +++ b/python/tk-core/python/sgtk/__init__.py @@ -12,12 +12,12 @@ ALT_API_NAME = "tank" THIS_MODULE_NAME = "sgtk" -# first import our alternative API -import tank - # now go through and duplicate all entries in sys.modules import sys +# first import our alternative API +import tank # noqa: F401 + # Generate a list of keys to iterate over, # since we'll be mutating the dict as we iterate. for x in list(sys.modules.keys()): diff --git a/python/tk-core/python/tank/__init__.py b/python/tk-core/python/tank/__init__.py index 2e553bd8..f720d254 100644 --- a/python/tk-core/python/tank/__init__.py +++ b/python/tk-core/python/tank/__init__.py @@ -35,34 +35,19 @@ import uuid import warnings -if sys.version_info < (3, 7): +if sys.version_info < (3, 9): if os.environ.get("SHOTGUN_ALLOW_OLD_PYTHON", "0") != "1": - # This is our preferred default behavior when using an old - # unsupported Python version. - # This way, we can control where the exception is raised, and it provides a - # comprehensive error message rather than having users facing a random - # Python traceback and trying to understand this is due to using an - # unsupported Python version. - - raise RuntimeError("This module requires Python version 3.7 or higher.") + raise RuntimeError("This module requires Python version 3.9 or higher.") warnings.warn( - "Python versions older than 3.7 are no longer supported as of January " - "2023. Since the SHOTGUN_ALLOW_OLD_PYTHON variable is enabled, this " + "Python versions older than 3.9 are no longer supported as of March " + "2025. Since the SHOTGUN_ALLOW_OLD_PYTHON variable is enabled, this " "module is raising a warning instead of an exception. " "However, it is very likely that this module will not be able to work " "on this Python version.", RuntimeWarning, stacklevel=2, ) -elif sys.version_info < (3, 9): - warnings.warn( - "Python versions older than 3.9 are no longer supported as of March " - "2025 and compatibility will be discontinued after March 2026. " - "Please update to Python 3.11 or any other supported version.", - DeprecationWarning, - stacklevel=2 - ) def __fix_tank_vendor(): @@ -134,57 +119,58 @@ def __fix_tank_vendor(): ######################################################################## # first import the log manager since a lot of modules require this. -from .log import LogManager +from .log import LogManager # isort: skip # make sure that all sub-modules are imported at the same as the main module -from . import authentication -from . import descriptor -from . import bootstrap -from . import commands -from . import deploy -from . import folder -from . import platform -from . import util +from . import ( + authentication, + bootstrap, + commands, + deploy, + descriptor, + folder, + platform, + util, +) # core functionality from .api import ( + Sgtk, Tank, - tank_from_path, - tank_from_entity, - set_authenticated_user, get_authenticated_user, + set_authenticated_user, + sgtk_from_entity, + sgtk_from_path, + tank_from_entity, + tank_from_path, ) -from .api import Sgtk, sgtk_from_path, sgtk_from_entity -from .pipelineconfig_utils import ( - get_python_interpreter_for_config, - get_core_python_path_for_config, - get_sgtk_module_path, -) +from .authentication.flow_auth import get_flow_access_token, get_flow_client +from .commands import CommandInteraction, SgtkSystemCommand, get_command, list_commands +# expose the support url +from .constants import ( + DEFAULT_STORAGE_ROOT_HOOK_NAME, +) +from .constants import SUPPORT_URL as support_url from .context import Context - from .errors import ( TankError, TankErrorProjectIsSetup, - TankHookMethodDoesNotExistError, TankFileDoesNotExistError, - TankUnreadableFileError, - TankInvalidInterpreterLocationError, + TankHookMethodDoesNotExistError, TankInvalidCoreLocationError, + TankInvalidInterpreterLocationError, TankNotPipelineConfigurationError, + TankUnreadableFileError, +) +from .hook import Hook, get_hook_baseclass +from .pipelineconfig_utils import ( + get_core_python_path_for_config, + get_python_interpreter_for_config, + get_sgtk_module_path, ) # note: TankEngineInitError used to reside in .errors but was moved into platform.errors from .platform.errors import TankEngineInitError from .template import Template, TemplatePath, TemplateString -from .hook import Hook, get_hook_baseclass - -from .commands import list_commands, get_command, SgtkSystemCommand, CommandInteraction - -from .templatekey import TemplateKey, SequenceKey, IntegerKey, StringKey, TimestampKey - -# expose the support url -from .constants import ( - DEFAULT_STORAGE_ROOT_HOOK_NAME, - SUPPORT_URL as support_url, -) +from .templatekey import IntegerKey, SequenceKey, StringKey, TemplateKey, TimestampKey diff --git a/python/tk-core/python/tank/api.py b/python/tk-core/python/tank/api.py index cca816e1..03478832 100644 --- a/python/tk-core/python/tank/api.py +++ b/python/tk-core/python/tank/api.py @@ -12,20 +12,22 @@ Classes for the main Sgtk API. """ -import os import glob +import os -from . import folder -from . import context -from .util import shotgun, yaml_cache +from . import ( + LogManager, + constants, + context, + folder, + pipelineconfig, + pipelineconfig_factory, + pipelineconfig_utils, +) from .errors import TankError, TankMultipleMatchingTemplatesError from .path_cache import PathCache from .template import read_templates -from . import constants -from . import pipelineconfig -from . import pipelineconfig_utils -from . import pipelineconfig_factory -from . import LogManager +from .util import shotgun, yaml_cache log = LogManager.get_logger(__name__) @@ -38,7 +40,7 @@ class Sgtk(object): manipulation and the Toolkit template system. """ - (DEFAULT, CENTRALIZED, DISTRIBUTED) = range(3) + DEFAULT, CENTRALIZED, DISTRIBUTED = range(3) def __init__(self, project_path): """ @@ -1032,7 +1034,6 @@ def get_authenticated_user(): :returns: A :class:`~sgtk.authentication.ShotgunUser` derived object if set, None otherwise. """ - global _authenticated_user return _authenticated_user diff --git a/python/tk-core/python/tank/authentication/__init__.py b/python/tk-core/python/tank/authentication/__init__.py index f9125528..f669b96d 100644 --- a/python/tk-core/python/tank/authentication/__init__.py +++ b/python/tk-core/python/tank/authentication/__init__.py @@ -18,27 +18,28 @@ credentials are reused if available. """ +from .core_defaults_manager import CoreDefaultsManager +from .defaults_manager import DefaultsManager +from .errors import ConsoleLoginWithSSONotSupportedError # For backward compatibility. from .errors import ( # noqa AuthenticationCancelled, AuthenticationError, - ConsoleLoginWithSSONotSupportedError, # For backward compatibility. ConsoleLoginNotSupportedError, IncompleteCredentials, ShotgunAuthenticationError, UnresolvableHumanUser, UnresolvableScriptUser, ) -from .web_login_support import ( - get_shotgun_authenticator_support_web_login, - set_shotgun_authenticator_support_web_login, -) +from .flow_auth import FlowAuthenticationHandler, get_flow_access_token, get_flow_client from .shotgun_authenticator import ShotgunAuthenticator -from .defaults_manager import DefaultsManager -from .core_defaults_manager import CoreDefaultsManager from .user import ( # noqa - deserialize_user, - serialize_user, ShotgunSamlUser, ShotgunUser, ShotgunWebUser, + deserialize_user, + serialize_user, +) +from .web_login_support import ( + get_shotgun_authenticator_support_web_login, + set_shotgun_authenticator_support_web_login, ) diff --git a/python/tk-core/python/tank/authentication/app_session_launcher.py b/python/tk-core/python/tank/authentication/app_session_launcher.py index 0e7762db..c48d260e 100644 --- a/python/tk-core/python/tank/authentication/app_session_launcher.py +++ b/python/tk-core/python/tank/authentication/app_session_launcher.py @@ -89,7 +89,7 @@ def process( url_handlers.append( urllib.request.HTTPSHandler( - context = ssl.create_default_context( + context=ssl.create_default_context( cafile=ca_certs, ), ), @@ -255,10 +255,12 @@ def process( elif response_code_major == 3: location = response.headers.get("location", None) - logger.debug("Request redirected: http code: {code}; redirect to: {location}".format( - code=response.code, - location=location, - )) + logger.debug( + "Request redirected: http code: {code}; redirect to: {location}".format( + code=response.code, + location=location, + ) + ) raise AuthenticationError( "Request redirected", @@ -286,9 +288,11 @@ def process( ) elif response.code != http.client.OK: - logger.debug("Request denied: http code is: {code}".format( - code=response.code, - )) + logger.debug( + "Request denied: http code is: {code}".format( + code=response.code, + ) + ) raise AuthenticationError( "Request denied", payload=getattr(response, "json", response), @@ -503,9 +507,11 @@ def build_user_agent(): lh = logging.StreamHandler() lh.setLevel(logging.DEBUG) - lh.setFormatter(logging.Formatter( - "%(asctime)s - %(levelname)s - %(message)s", - )) + lh.setFormatter( + logging.Formatter( + "%(asctime)s - %(levelname)s - %(message)s", + ) + ) logger.addHandler(lh) print() diff --git a/python/tk-core/python/tank/authentication/console_authentication.py b/python/tk-core/python/tank/authentication/console_authentication.py index cde2ddc7..cebcb995 100644 --- a/python/tk-core/python/tank/authentication/console_authentication.py +++ b/python/tk-core/python/tank/authentication/console_authentication.py @@ -19,23 +19,21 @@ -------------------------------------------------------------------------------- """ -from . import constants -from . import session_cache +import webbrowser +from getpass import getpass + +from tank_vendor.shotgun_api3 import MissingTwoFactorAuthenticationFault + from .. import LogManager +from ..util import metrics_cache +from ..util.metrics import EventMetric +from ..util.shotgun.connection import sanitize_url +from . import app_session_launcher, constants, session_cache, site_info from .errors import ( - AuthenticationError, AuthenticationCancelled, + AuthenticationError, ConsoleLoginNotSupportedError, ) -from tank_vendor.shotgun_api3 import MissingTwoFactorAuthenticationFault -from . import site_info -from . import app_session_launcher -from ..util import metrics_cache -from ..util.metrics import EventMetric -from ..util.shotgun.connection import sanitize_url - -from getpass import getpass -import webbrowser logger = LogManager.get_logger(__name__) diff --git a/python/tk-core/python/tank/authentication/constants.py b/python/tk-core/python/tank/authentication/constants.py index b3bf7194..28972b34 100644 --- a/python/tk-core/python/tank/authentication/constants.py +++ b/python/tk-core/python/tank/authentication/constants.py @@ -20,8 +20,6 @@ def method_resolve_reverse(m): - global method_resolve - - for (k, v) in method_resolve.items(): + for k, v in method_resolve.items(): if v == m: return k diff --git a/python/tk-core/python/tank/authentication/core_defaults_manager.py b/python/tk-core/python/tank/authentication/core_defaults_manager.py index 0f3c2f0e..aa7c0db9 100644 --- a/python/tk-core/python/tank/authentication/core_defaults_manager.py +++ b/python/tk-core/python/tank/authentication/core_defaults_manager.py @@ -14,8 +14,8 @@ been configured with the core, its credentials will also be provided. """ -from .defaults_manager import DefaultsManager from ..util import shotgun +from .defaults_manager import DefaultsManager class CoreDefaultsManager(DefaultsManager): diff --git a/python/tk-core/python/tank/authentication/defaults_manager.py b/python/tk-core/python/tank/authentication/defaults_manager.py index 886b5c11..d23026f6 100644 --- a/python/tk-core/python/tank/authentication/defaults_manager.py +++ b/python/tk-core/python/tank/authentication/defaults_manager.py @@ -8,9 +8,9 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -from . import session_cache -from ..util.user_settings import UserSettings from ..util.system_settings import SystemSettings +from ..util.user_settings import UserSettings +from . import session_cache class DefaultsManager(object): diff --git a/python/tk-core/python/tank/authentication/errors.py b/python/tk-core/python/tank/authentication/errors.py index b7fe9330..8bee559c 100644 --- a/python/tk-core/python/tank/authentication/errors.py +++ b/python/tk-core/python/tank/authentication/errors.py @@ -73,9 +73,7 @@ def __init__(self, login): """ :param str login: ``login`` field value of the ``HumanUser`` that could not be resolved. """ - super().__init__( - "person", "HumanUser", "login", login - ) + super().__init__("person", "HumanUser", "login", login) class UnresolvableScriptUser(UnresolvableUser): @@ -87,9 +85,7 @@ def __init__(self, script_name): """ :param str script_name: ``firstname`` field value of the ``ApiUser`` that could not be resolved. """ - super().__init__( - "script", "ApiUser", "firstname", script_name - ) + super().__init__("script", "ApiUser", "firstname", script_name) class ConsoleLoginNotSupportedError(ShotgunAuthenticationError): diff --git a/python/tk-core/python/tank/authentication/flow_auth/__init__.py b/python/tk-core/python/tank/authentication/flow_auth/__init__.py new file mode 100644 index 00000000..a244922e --- /dev/null +++ b/python/tk-core/python/tank/authentication/flow_auth/__init__.py @@ -0,0 +1,41 @@ +# Copyright (c) 2026 Shotgun Software Inc. +# +# CONFIDENTIAL AND PROPRIETARY +# +# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit +# Source Code License included in this distribution package. See LICENSE. +# By accessing, using, copying or modifying this work you indicate your +# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights +# not expressly granted therein are reserved by Shotgun Software Inc. + +""" +Flow / MEDM authentication for Toolkit bootstrap. + +Triggered during bootstrap when a project is "AM-ready". Obtains an APS +access token via PKCE; the token is cached in a local file store for reuse. +""" + +from ._authentication import ( + check_token_expiry, + get_access_token, + get_flow_access_token, + init_authentication, +) +from ._client import FlowAuthenticationHandler, get_flow_client +from ._constants import AM_READY_PROJECT_FIELD +from ._settings import FlowAuthSettings, resolve_flow_auth_settings +from .errors import FlowAuthConfigurationError, FlowAuthError + +__all__ = [ + "init_authentication", + "get_access_token", + "get_flow_access_token", + "check_token_expiry", + "FlowAuthenticationHandler", + "get_flow_client", + "FlowAuthSettings", + "resolve_flow_auth_settings", + "FlowAuthError", + "FlowAuthConfigurationError", + "AM_READY_PROJECT_FIELD", +] diff --git a/python/tk-core/python/tank/authentication/flow_auth/_authentication.py b/python/tk-core/python/tank/authentication/flow_auth/_authentication.py new file mode 100644 index 00000000..1ee18349 --- /dev/null +++ b/python/tk-core/python/tank/authentication/flow_auth/_authentication.py @@ -0,0 +1,193 @@ +# Copyright (c) 2026 Shotgun Software Inc. +# +# CONFIDENTIAL AND PROPRIETARY +# +# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit +# Source Code License included in this distribution package. See LICENSE. +# By accessing, using, copying or modifying this work you indicate your +# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights +# not expressly granted therein are reserved by Shotgun Software Inc. +""" +This module contains utilities for authenticating into Flow. +""" + +from __future__ import annotations + +import base64 +import json +import time + +from tank_vendor.adsk_auth import ( + AuthConfig, +) +from tank_vendor.adsk_auth import get_access_token as get_access_token_from_adsk_auth + +from ... import LogManager +from ...util import LocalFileStorageManager +from ._constants import REQUIRED_SCOPES +from .errors import FlowAuthConfigurationError + +logger = LogManager.get_logger(__name__) + +_aps_configuration = None + + +def init_authentication(settings): + """Initialize authentication configuration with configured settings. + + Args: + settings: FlowAuthSettings (or duck-typed equivalent) containing + auth_application_id, auth_base_url, auth_callback_url. + + Raises: + FlowAuthConfigurationError: If required authentication settings are missing or invalid. + """ + global _aps_configuration + + auth_application_id = settings.auth_application_id + auth_base_url = settings.auth_base_url + auth_callback_url = settings.auth_callback_url + + if not auth_application_id: + raise FlowAuthConfigurationError( + details="Required setting 'auth_application_id' is not configured." + ) + if not auth_base_url: + raise FlowAuthConfigurationError( + details="Required setting 'auth_base_url' is not configured." + ) + if not auth_callback_url: + raise FlowAuthConfigurationError( + details="Required setting 'auth_callback_url' is not configured." + ) + + _aps_configuration = AuthConfig( + application_id=auth_application_id, + base_url=auth_base_url, + callback_url=auth_callback_url, + description="Autodesk Toolkit", + required_application_scopes=REQUIRED_SCOPES, + storage_dir=LocalFileStorageManager.get_global_root( + LocalFileStorageManager.CACHE + ), + ) + + +def _get_aps_configuration(): + """Get the APS configuration. + + Returns: + tank_vendor.adsk_auth.AuthConfig: The initialized auth configuration. + + Raises: + RuntimeError: If authentication has not been initialized. + """ + if _aps_configuration is None: + raise RuntimeError( + "Authentication not initialized. Call init_authentication() first." + ) + return _aps_configuration + + +def check_token_expiry(token: str, buffer_seconds: int = 300) -> bool: + """ + Check if the given token is expiring soon (within the buffer period). + + The Flow API can fail if the token is not yet expired but will expire soon, + so we proactively refresh when within the buffer. + + Args: + token: The access token to check. + buffer_seconds: Seconds before expiry to consider the token "expiring soon". + Defaults to 300 (5 minutes). + + Returns: + True if the token is expiring soon or invalid, False if it has + sufficient validity remaining. + """ + try: + payload = _decode_token_payload(token) + except Exception as e: + logger.error("Error decoding token: %s", e) + return True + + exp_timestamp = payload.get("exp") if payload else None + + if not exp_timestamp: + logger.warning( + "Token does not contain 'exp' claim. Treating token as expiring soon." + ) + return True + + current_timestamp = time.time() + time_remaining = exp_timestamp - current_timestamp + if time_remaining < buffer_seconds: + logger.debug( + "Token will expire in %.0f seconds, less than buffer of %s seconds.", + time_remaining, + buffer_seconds, + ) + return True + + return False + + +def _decode_token_payload(token: str): + """Decode JWT payload without verification (only used to read exp).""" + try: + parts = token.split(".") + if len(parts) != 3: + return None + payload_b64 = parts[1] + padding = (4 - len(payload_b64) % 4) % 4 + payload_b64 += "=" * padding + payload_bytes = base64.urlsafe_b64decode(payload_b64) + return json.loads(payload_bytes) + except Exception: + return None + + +def _auth_options_from_kwargs(kwargs): + """Extract adsk_auth get_access_token options from kwargs (e.g. authentication_options).""" + opts = kwargs.get("authentication_options") + if opts is None: + return {} + return { + "profile": getattr(opts, "user_profile", None), + "force_refresh": getattr(opts, "force_refresh", False), + "force_reauthentication": getattr(opts, "force_reauthentication", False), + "time_out": getattr(opts, "time_out", 30.0), + "browser": getattr(opts, "browser", None), + } + + +def get_access_token(*args, **kwargs) -> str: + """Get access token from file store or web (PKCE). + + Ensures the returned token has at least 5 minutes of validity remaining, + since the Flow API can fail when the token is about to expire. + """ + config = _get_aps_configuration() + options = _auth_options_from_kwargs(kwargs) + token = get_access_token_from_adsk_auth(config, **options) + + if check_token_expiry(token): + logger.info("Access token is expiring within 5 minutes. Forcing a refresh.") + options["force_refresh"] = True + token = get_access_token_from_adsk_auth(config, **options) + + return token + + +def get_flow_access_token(**kwargs) -> str: + """Get a Flow access token, lazy-initialising auth if needed. + + Safe to call without a prior ``init_authentication()`` — if bootstrap has + not already initialised the APS configuration, settings are resolved from + environment variables and initialisation is performed automatically. + """ + if _aps_configuration is None: + from ._settings import resolve_flow_auth_settings + + init_authentication(resolve_flow_auth_settings()) + return get_access_token(**kwargs) diff --git a/python/tk-core/python/tank/authentication/flow_auth/_client.py b/python/tk-core/python/tank/authentication/flow_auth/_client.py new file mode 100644 index 00000000..db00aff2 --- /dev/null +++ b/python/tk-core/python/tank/authentication/flow_auth/_client.py @@ -0,0 +1,49 @@ +# Copyright (c) 2026 Shotgun Software Inc. +# +# CONFIDENTIAL AND PROPRIETARY +# +# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit +# Source Code License included in this distribution package. See LICENSE. +# By accessing, using, copying or modifying this work you indicate your +# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights +# not expressly granted therein are reserved by Shotgun Software Inc. + +""" +Flow GQL SDK client factory with authentication wired in. +""" + +from ._authentication import get_flow_access_token + + +class FlowAuthenticationHandler: + """Auth adapter for the Flow GQL SDK client. + + Satisfies the SDK's ``auth_handler`` interface: the client calls + ``get_authentication_token()`` on every request so that short-lived tokens + are transparently refreshed without recreating the client. + """ + + def get_authentication_token(self) -> str: + return get_flow_access_token() + + +# TODO: replace with proper client entry point +def get_flow_client(endpoint_url=None): + """Return a ready-to-use Flow GQL SDK client with authentication wired in. + + Lazy-initialises APS auth if bootstrap has not already done so. Uses the + SDK default endpoint (``tank_vendor.flow_data_sdk.config.DEFAULT_ENDPOINT``) + when ``endpoint_url`` is not supplied. + + :param endpoint_url: Override the GraphQL endpoint. Defaults to the SDK's + production endpoint. + :type endpoint_url: str or None + :returns: Initialised ``GQLClient`` instance. + """ + from tank_vendor.flow_data_sdk import GQLClient + from tank_vendor.flow_data_sdk.config import DEFAULT_ENDPOINT + + return GQLClient( + endpoint=endpoint_url or DEFAULT_ENDPOINT, + auth_handler=FlowAuthenticationHandler(), + ) diff --git a/python/tk-core/python/tank/authentication/flow_auth/_constants.py b/python/tk-core/python/tank/authentication/flow_auth/_constants.py new file mode 100644 index 00000000..b4c0b540 --- /dev/null +++ b/python/tk-core/python/tank/authentication/flow_auth/_constants.py @@ -0,0 +1,26 @@ +# Copyright (c) 2026 Shotgun Software Inc. +# +# CONFIDENTIAL AND PROPRIETARY +# +# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit +# Source Code License included in this distribution package. See LICENSE. +# By accessing, using, copying or modifying this work you indicate your +# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights +# not expressly granted therein are reserved by Shotgun Software Inc. + +"""Constants for Flow / MEDM authentication.""" + +AM_READY_PROJECT_FIELD = "sg_flow_am_id" + +DEFAULT_AUTH_APPLICATION_ID = "8QyoQKXZ7HDuQFmptJGrzsp2GwpATmyV" +DEFAULT_AUTH_BASE_URL = "https://api.aps.usa.autodesk.com" +DEFAULT_AUTH_CALLBACK_URL = "http://localhost:4201/auth/callback" + +# Previously "openid" was also requested but is not used or required for +# authentication to Flow, and exceeded Windows Credential Manager's 1280-char +# limit. Safe to exclude. +REQUIRED_SCOPES = [ + "data:read", + "data:write", + "data:create", +] diff --git a/python/tk-core/python/tank/authentication/flow_auth/_settings.py b/python/tk-core/python/tank/authentication/flow_auth/_settings.py new file mode 100644 index 00000000..1a276ce9 --- /dev/null +++ b/python/tk-core/python/tank/authentication/flow_auth/_settings.py @@ -0,0 +1,51 @@ +# Copyright (c) 2026 Shotgun Software Inc. +# +# CONFIDENTIAL AND PROPRIETARY +# +# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit +# Source Code License included in this distribution package. See LICENSE. +# By accessing, using, copying or modifying this work you indicate your +# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights +# not expressly granted therein are reserved by Shotgun Software Inc. + +"""Settings resolver for Flow / MEDM authentication.""" + +from __future__ import annotations + +import os +from dataclasses import dataclass + +from ._constants import ( + DEFAULT_AUTH_APPLICATION_ID, + DEFAULT_AUTH_BASE_URL, + DEFAULT_AUTH_CALLBACK_URL, +) + + +@dataclass +class FlowAuthSettings: + """APS credentials required to perform PKCE authentication.""" + + auth_application_id: str + auth_base_url: str + auth_callback_url: str + + +def resolve_flow_auth_settings() -> FlowAuthSettings: + """ + Resolve APS auth settings: env-var overrides, falling back to hardcoded defaults. + + Override env vars: + TK_FLOW_AUTH_APPLICATION_ID + TK_FLOW_AUTH_BASE_URL + TK_FLOW_AUTH_CALLBACK_URL + """ + return FlowAuthSettings( + auth_application_id=os.environ.get( + "TK_FLOW_AUTH_APPLICATION_ID", DEFAULT_AUTH_APPLICATION_ID + ), + auth_base_url=os.environ.get("TK_FLOW_AUTH_BASE_URL", DEFAULT_AUTH_BASE_URL), + auth_callback_url=os.environ.get( + "TK_FLOW_AUTH_CALLBACK_URL", DEFAULT_AUTH_CALLBACK_URL + ), + ) diff --git a/python/tk-core/python/tank/authentication/flow_auth/errors.py b/python/tk-core/python/tank/authentication/flow_auth/errors.py new file mode 100644 index 00000000..bc9c3f6c --- /dev/null +++ b/python/tk-core/python/tank/authentication/flow_auth/errors.py @@ -0,0 +1,25 @@ +# Copyright (c) 2026 Shotgun Software Inc. +# +# CONFIDENTIAL AND PROPRIETARY +# +# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit +# Source Code License included in this distribution package. See LICENSE. +# By accessing, using, copying or modifying this work you indicate your +# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights +# not expressly granted therein are reserved by Shotgun Software Inc. + +"""Errors for Flow / MEDM authentication.""" + +from ...errors import TankError + + +class FlowAuthError(TankError): + """Base exception for Flow / MEDM authentication errors.""" + + +class FlowAuthConfigurationError(FlowAuthError): + """Raised when Flow auth settings are missing or invalid.""" + + def __init__(self, details: str = ""): + super().__init__(details) + self.details = details diff --git a/python/tk-core/python/tank/authentication/interactive_authentication.py b/python/tk-core/python/tank/authentication/interactive_authentication.py index d5ca7a94..d68a267c 100644 --- a/python/tk-core/python/tank/authentication/interactive_authentication.py +++ b/python/tk-core/python/tank/authentication/interactive_authentication.py @@ -24,15 +24,14 @@ # Using "with" with the lock to make sure it is always released. -from .errors import AuthenticationCancelled -from .console_authentication import ConsoleLoginHandler, ConsoleRenewSessionHandler - -from .. import LogManager - -import threading import os +import threading + from tank.util import is_windows +from .. import LogManager +from .console_authentication import ConsoleLoginHandler, ConsoleRenewSessionHandler +from .errors import AuthenticationCancelled logger = LogManager.get_logger(__name__) @@ -56,7 +55,7 @@ def _get_current_os_user(): pwd_entry = pwd.getpwuid(os.geteuid()) return pwd_entry[0] - except: + except Exception: return None @@ -78,6 +77,7 @@ def _get_ui_state() -> bool: return bool(QtGui and QtGui.QApplication.instance()) + class SessionRenewal(object): """ Handles multi-threaded session renewal. This class handles the use case when @@ -220,6 +220,7 @@ def renew_session(user): # If we have a gui, we need gui based authentication if has_ui: from .ui_authentication import UiAuthenticationHandler + authenticator = UiAuthenticationHandler( is_session_renewal=True, session_metadata=user.get_session_metadata() ) diff --git a/python/tk-core/python/tank/authentication/login_dialog.py b/python/tk-core/python/tank/authentication/login_dialog.py index 46311c8d..1b7542f3 100644 --- a/python/tk-core/python/tank/authentication/login_dialog.py +++ b/python/tk-core/python/tank/authentication/login_dialog.py @@ -17,37 +17,34 @@ at any point. -------------------------------------------------------------------------------- """ + import os import sys + from tank_vendor import shotgun_api3 -from .. import constants -from .web_login_support import get_shotgun_authenticator_support_web_login -from .ui import resources_rc # noqa -from .ui import login_dialog -from . import constants as auth_constants -from . import session_cache + +from .. import LogManager, constants +from ..util import LocalFileStorageManager, login, metrics_cache from ..util.metrics import EventMetric from ..util.shotgun import connection -from ..util import login -from ..util import LocalFileStorageManager -from ..util import metrics_cache -from .errors import AuthenticationError -from .ui.qt_abstraction import ( - QtGui, - QtCore, - QtNetwork, - QtWebEngineWidgets, - qt_version_tuple, -) from . import app_session_launcher -from . import site_info +from . import constants as auth_constants +from . import session_cache, site_info +from .errors import AuthenticationError from .sso_saml2 import ( SsoSaml2IncompletePySide2, SsoSaml2MissingQtModuleError, ) from .sso_saml2.sso_saml2_toolkit import SsoSaml2Toolkit - -from .. import LogManager +from .ui import resources_rc # noqa +from .ui import login_dialog +from .ui.qt_abstraction import ( + QtCore, + QtGui, + QtNetwork, + QtWebEngineWidgets, +) +from .web_login_support import get_shotgun_authenticator_support_web_login logger = LogManager.get_logger(__name__) @@ -109,6 +106,7 @@ def run(self): """ self._site_info.reload(self._url_to_test, self._http_proxy) + class LoginDialog(QtGui.QDialog): """ Dialog for getting user credentials. @@ -315,6 +313,16 @@ def __init__( % self._get_current_site() ) + # QThread.finished is delivered through the Qt event queue and requires + # the event loop to be running. Since we are still in the constructor, + # the _toggle_web slot has not been called yet even though wait() + # returned and the thread is done. Call it directly here so the dialog + # opens with the correct UI (e.g. Identity/SSO sites must not flash the + # 3-fields credentials form before switching to the web-login view). + # The guard inside _toggle_web makes the subsequent signal delivery a + # no-op, so this is safe. + self._toggle_web() + # Initialize exit confirm message box self.confirm_box = QtGui.QMessageBox( QtGui.QMessageBox.Question, @@ -484,15 +492,21 @@ def _toggle_web(self, method_selected=None): # - they need to use the legacy login / passphrase to use a PAT with # Autodesk Identity authentication if os.environ.get("SGTK_FORCE_STANDARD_LOGIN_DIALOG"): - logger.info("Using the standard login dialog with the Flow Production Tracking") + logger.info( + "Using the standard login dialog with the Flow Production Tracking" + ) else: if _is_running_in_desktop(): - can_use_web = can_use_web or self.site_info.autodesk_identity_enabled + can_use_web = ( + can_use_web or self.site_info.autodesk_identity_enabled + ) # If we have full support for Web-based login, or if we enable it in our # environment, use the Unified Login Flow for all authentication modes. if get_shotgun_authenticator_support_web_login(): - can_use_web = can_use_web or self.site_info.unified_login_flow_enabled + can_use_web = ( + can_use_web or self.site_info.unified_login_flow_enabled + ) if method_selected: # Selecting requested mode (credentials, qt_web_login or app_session_launcher) @@ -504,9 +518,7 @@ def _toggle_web(self, method_selected=None): method_selected = session_cache.get_preferred_method(site) # Make sure that the method_selected is currently supported - if ( - method_selected == auth_constants.METHOD_WEB_LOGIN and not can_use_web - ) or ( + if (method_selected == auth_constants.METHOD_WEB_LOGIN and not can_use_web) or ( method_selected == auth_constants.METHOD_ASL and not can_use_asl ): method_selected = None @@ -518,9 +530,7 @@ def _toggle_web(self, method_selected=None): ) # Make sure that the method_selected is currently supported - if ( - method_selected == auth_constants.METHOD_WEB_LOGIN and not can_use_web - ) or ( + if (method_selected == auth_constants.METHOD_WEB_LOGIN and not can_use_web) or ( method_selected == auth_constants.METHOD_ASL and not can_use_asl ): method_selected = None @@ -691,7 +701,9 @@ def result(self): "Logged In", properties={ "authentication_method": self.site_info.user_authentication_method, - "authentication_experience": auth_constants.method_resolve.get(self.method_selected), + "authentication_experience": auth_constants.method_resolve.get( + self.method_selected + ), "authentication_interface": "qt_dialog", "authentication_renewal": self._is_session_renewal, }, @@ -760,7 +772,10 @@ def _ok_pressed(self): # Cleanup the URL and update the GUI. if self.method_selected != auth_constants.METHOD_BASIC: - if site.startswith("http://") and "SGTK_AUTH_ALLOW_NO_HTTPS" not in os.environ: + if ( + site.startswith("http://") + and "SGTK_AUTH_ALLOW_NO_HTTPS" not in os.environ + ): site = "https" + site[4:] self.ui.site.setEditText(site) diff --git a/python/tk-core/python/tank/authentication/session_cache.py b/python/tk-core/python/tank/authentication/session_cache.py index 7f10e1e9..7b66ec6a 100644 --- a/python/tk-core/python/tank/authentication/session_cache.py +++ b/python/tk-core/python/tank/authentication/session_cache.py @@ -21,19 +21,21 @@ import os import socket + +from tank_vendor import yaml from tank_vendor.shotgun_api3 import ( - Shotgun, AuthenticationFault, - ProtocolError, MissingTwoFactorAuthenticationFault, + ProtocolError, + Shotgun, ) from tank_vendor.shotgun_api3.lib import httplib2 -from tank_vendor import yaml -from . import constants -from .errors import AuthenticationError + from .. import LogManager -from ..util.shotgun import connection from ..util import LocalFileStorageManager +from ..util.shotgun import connection +from . import constants +from .errors import AuthenticationError logger = LogManager.get_logger(__name__) @@ -222,7 +224,7 @@ def _try_load_site_authentication_file(file_path): content.setdefault(_RECENT_USERS, []) if content.get(_PREFERRED_METHOD, "not null") is None: - del(content[_PREFERRED_METHOD]) + del content[_PREFERRED_METHOD] for user in content[_USERS]: user[_LOGIN] = user[_LOGIN].strip() diff --git a/python/tk-core/python/tank/authentication/shotgun_authenticator.py b/python/tk-core/python/tank/authentication/shotgun_authenticator.py index 7987650d..5f15685c 100644 --- a/python/tk-core/python/tank/authentication/shotgun_authenticator.py +++ b/python/tk-core/python/tank/authentication/shotgun_authenticator.py @@ -10,14 +10,11 @@ """PTR Authenticator.""" -from .sso_saml2 import has_sso_info_in_cookies, has_unified_login_flow_info_in_cookies -from . import interactive_authentication -from . import user -from . import user_impl -from . import session_cache -from .errors import IncompleteCredentials -from .defaults_manager import DefaultsManager from .. import LogManager +from . import interactive_authentication, session_cache, user, user_impl +from .defaults_manager import DefaultsManager +from .errors import IncompleteCredentials +from .sso_saml2 import has_sso_info_in_cookies, has_unified_login_flow_info_in_cookies logger = LogManager.get_logger(__name__) diff --git a/python/tk-core/python/tank/authentication/shotgun_wrapper.py b/python/tk-core/python/tank/authentication/shotgun_wrapper.py index 965b2bc6..a6a17d62 100644 --- a/python/tk-core/python/tank/authentication/shotgun_wrapper.py +++ b/python/tk-core/python/tank/authentication/shotgun_wrapper.py @@ -15,12 +15,14 @@ at any point. -------------------------------------------------------------------------------- """ -import http.client -from tank_vendor.shotgun_api3 import Shotgun, AuthenticationFault +import http.client from xmlrpc.client import ProtocolError -from . import interactive_authentication, session_cache + +from tank_vendor.shotgun_api3 import AuthenticationFault, Shotgun + from .. import LogManager +from . import interactive_authentication, session_cache logger = LogManager.get_logger(__name__) diff --git a/python/tk-core/python/tank/authentication/site_info.py b/python/tk-core/python/tank/authentication/site_info.py index 1689c1b2..7b45d2e0 100644 --- a/python/tk-core/python/tank/authentication/site_info.py +++ b/python/tk-core/python/tank/authentication/site_info.py @@ -11,11 +11,10 @@ import time -from . import utils - from tank_vendor import shotgun_api3 from .. import LogManager +from . import utils logger = LogManager.get_logger(__name__) @@ -53,7 +52,9 @@ def _get_site_infos(url, http_proxy=None): # require authentication. http_proxy = utils.sanitize_http_proxy(http_proxy).netloc if http_proxy: - logger.debug("Using HTTP proxy to connect to the PTR server: %s", http_proxy) + logger.debug( + "Using HTTP proxy to connect to the PTR server: %s", http_proxy + ) logger.info("Infos for site '%s' not in cache or expired", url) sg = shotgun_api3.Shotgun( diff --git a/python/tk-core/python/tank/authentication/sso_saml2/__init__.py b/python/tk-core/python/tank/authentication/sso_saml2/__init__.py index f329b56d..bf460261 100644 --- a/python/tk-core/python/tank/authentication/sso_saml2/__init__.py +++ b/python/tk-core/python/tank/authentication/sso_saml2/__init__.py @@ -21,6 +21,10 @@ SsoSaml2MissingQtNetwork, SsoSaml2MultiSessionNotSupportedError, ) +from .core.utils import ( # noqa + get_logger, + set_logger_parent, +) # Functions from .utils import ( # noqa @@ -28,8 +32,3 @@ has_sso_info_in_cookies, has_unified_login_flow_info_in_cookies, ) - -from .core.utils import ( # noqa - get_logger, - set_logger_parent, -) diff --git a/python/tk-core/python/tank/authentication/sso_saml2/core/__init__.py b/python/tk-core/python/tank/authentication/sso_saml2/core/__init__.py index 3b92ed9b..a31a8fb4 100644 --- a/python/tk-core/python/tank/authentication/sso_saml2/core/__init__.py +++ b/python/tk-core/python/tank/authentication/sso_saml2/core/__init__.py @@ -11,6 +11,7 @@ This module contains files which are shared between RV and Toolkit. """ + def __getattr__(name): """ Retro compatibility - SG-40049 - Temporary workaround for compatibility for @@ -22,7 +23,7 @@ def __getattr__(name): https://github.com/shotgunsoftware/tk-nuke/blob/v0.16.0/engine.py#L396 This was removed in https://github.com/shotgunsoftware/tk-nuke/pull/125. - TODO: Remove this after 2026-07. + TODO: Remove this after 2026-07. """ try: @@ -33,6 +34,7 @@ def __getattr__(name): try: import inspect + frame = inspect.currentframe().f_back caller_mod = frame.f_globals.get("__name__", "") is_import = caller_mod.startswith("importlib.") or caller_mod == "builtins" @@ -50,6 +52,7 @@ def __getattr__(name): ) import warnings + warnings.warn( deprecation_message, DeprecationWarning, @@ -58,10 +61,12 @@ def __getattr__(name): try: import tank + logger = tank.LogManager.get_logger(__name__) logger.warning(deprecation_message) except: - pass # nosec + pass # nosec import importlib + return importlib.import_module(f"{__name__}.{name}") diff --git a/python/tk-core/python/tank/authentication/sso_saml2/core/sso_saml2_core.py b/python/tk-core/python/tank/authentication/sso_saml2/core/sso_saml2_core.py index 4c5d9d72..c42e72e8 100644 --- a/python/tk-core/python/tank/authentication/sso_saml2/core/sso_saml2_core.py +++ b/python/tk-core/python/tank/authentication/sso_saml2/core/sso_saml2_core.py @@ -21,6 +21,7 @@ import sys import time +from ...utils import sanitize_http_proxy from .authentication_session_data import AuthenticationSessionData from .errors import ( SsoSaml2IncompletePySide2, @@ -39,7 +40,6 @@ get_session_id, get_user_name, ) -from ...utils import sanitize_http_proxy try: from .username_password_dialog import UsernamePasswordDialog @@ -129,7 +129,9 @@ def get_renew_path(session, logger): # When this variable is set, it is passed to Autodesk Identity's login. tk_shotgun_default_login = os.getenv("TK_SHOTGRID_DEFAULT_LOGIN") # When this variable is set for a SSO domain, skip the initial login page. - tk_shotgun_sso_domain = sanitize_http_proxy(os.getenv("TK_SHOTGRID_SSO_DOMAIN")).netloc + tk_shotgun_sso_domain = sanitize_http_proxy( + os.getenv("TK_SHOTGRID_SSO_DOMAIN") + ).netloc # Flow Production Tracking's renew endpoint supports some useful # Autodesk Identity params. @@ -210,6 +212,7 @@ def __init__(self, window_title="Web Login", qt_modules=None): ) if QtNetwork and not hasattr(QtNetwork, "QSslConfiguration"): raise SsoSaml2IncompletePySide2("Missing class QtNetwork.QSslConfiguration") + class TKWebPageQtWebEngine(QtWebEngineWidgets.QWebEnginePage): """ Wrapper class to better control the behaviour when clicking on links diff --git a/python/tk-core/python/tank/authentication/sso_saml2/core/username_password_dialog.py b/python/tk-core/python/tank/authentication/sso_saml2/core/username_password_dialog.py index 963957ba..80d27e3f 100644 --- a/python/tk-core/python/tank/authentication/sso_saml2/core/username_password_dialog.py +++ b/python/tk-core/python/tank/authentication/sso_saml2/core/username_password_dialog.py @@ -75,8 +75,7 @@ def __init__(self, window_title=None, message=None): buttons.button(QtGui.QDialogButtonBox.Cancel).clicked.connect(self.close) # On Qt4, this sets the look-and-feel to that of the toolkit. - self.setStyleSheet( - """QWidget + self.setStyleSheet("""QWidget { background-color: rgb(36, 39, 42); color: rgb(192, 193, 195); @@ -98,8 +97,7 @@ def __init__(self, window_title=None, message=None): color: rgb(248, 248, 248); background-color: rgb(35, 165, 225); } - """ - ) + """) @property def username(self): diff --git a/python/tk-core/python/tank/authentication/sso_saml2/core/utils.py b/python/tk-core/python/tank/authentication/sso_saml2/core/utils.py index b5509e4e..492864b6 100644 --- a/python/tk-core/python/tank/authentication/sso_saml2/core/utils.py +++ b/python/tk-core/python/tank/authentication/sso_saml2/core/utils.py @@ -13,13 +13,12 @@ # pylint: disable=line-too-long -import sys import base64 import binascii import logging -from urllib.parse import unquote_plus +import sys from http.cookies import SimpleCookie - +from urllib.parse import unquote_plus from .errors import SsoSaml2MultiSessionNotSupportedError diff --git a/python/tk-core/python/tank/authentication/sso_saml2/sso_saml2.py b/python/tk-core/python/tank/authentication/sso_saml2/sso_saml2.py index af198d48..d1c7b0f6 100644 --- a/python/tk-core/python/tank/authentication/sso_saml2/sso_saml2.py +++ b/python/tk-core/python/tank/authentication/sso_saml2/sso_saml2.py @@ -17,7 +17,6 @@ # pylint: disable=unused-import from .core.sso_saml2_core import SsoSaml2Core # noqa - from .core.utils import ( # noqa set_logger_parent, ) diff --git a/python/tk-core/python/tank/authentication/sso_saml2/utils.py b/python/tk-core/python/tank/authentication/sso_saml2/utils.py index ef485fc6..bea8ef01 100644 --- a/python/tk-core/python/tank/authentication/sso_saml2/utils.py +++ b/python/tk-core/python/tank/authentication/sso_saml2/utils.py @@ -14,10 +14,10 @@ # pylint: disable=unused-import from .core.utils import ( # noqa + _decode_cookies, + _get_shotgun_user_id, get_saml_claims_expiration, get_session_expiration, - _decode_cookies, - _get_shotgun_user_id ) diff --git a/python/tk-core/python/tank/authentication/ui/aspect_preserving_label.py b/python/tk-core/python/tank/authentication/ui/aspect_preserving_label.py index 5e6db348..de53e3d4 100644 --- a/python/tk-core/python/tank/authentication/ui/aspect_preserving_label.py +++ b/python/tk-core/python/tank/authentication/ui/aspect_preserving_label.py @@ -17,6 +17,7 @@ class AspectPreservingLabel(QtGui.QLabel): Label that displays a scaled down version of an image if it is bigger than the label. """ + def __init__(self, parent=None): """ Constructor @@ -35,7 +36,8 @@ def setPixmap(self, pixmap): """ self._pix = pixmap scaled_pixmap = self._pix.scaled( - self.size(), QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation) + self.size(), QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation + ) QtGui.QLabel.setPixmap(self, scaled_pixmap) def heightForWidth(self, width): @@ -67,6 +69,7 @@ def resizeEvent(self, e): return scaled_pixmap = self._pix.scaled( - self.size(), QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation) + self.size(), QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation + ) QtGui.QLabel.setPixmap(self, scaled_pixmap) QtGui.QApplication.instance().processEvents() diff --git a/python/tk-core/python/tank/authentication/ui/completion_filter_proxy.py b/python/tk-core/python/tank/authentication/ui/completion_filter_proxy.py index de6462e9..e1e6b876 100644 --- a/python/tk-core/python/tank/authentication/ui/completion_filter_proxy.py +++ b/python/tk-core/python/tank/authentication/ui/completion_filter_proxy.py @@ -23,13 +23,14 @@ from .qt_abstraction import QtGui -class FuzzyMatcher(): +class FuzzyMatcher: """ Implement an algorithm to rank strings via fuzzy matching. Based on the analysis at http://crossplatform.net/sublime-text-ctrl-p-fuzzy-matching-in-python """ + def __init__(self, pattern): # construct a pattern that matches the letters in order # for example "aad" turns into "(a).*?(a).*?(d)". @@ -51,6 +52,7 @@ class CompletionFilterProxy(QtGui.QSortFilterProxyModel): """ Filters rows based on fuzzy matching and sorts them based on their score. """ + def __init__(self, parent): super().__init__(parent) self.set_filter("") diff --git a/python/tk-core/python/tank/authentication/ui/login_dialog.py b/python/tk-core/python/tank/authentication/ui/login_dialog.py index 8cf2f9fc..2b80db05 100644 --- a/python/tk-core/python/tank/authentication/ui/login_dialog.py +++ b/python/tk-core/python/tank/authentication/ui/login_dialog.py @@ -9,166 +9,175 @@ ################################################################################ from .qt_abstraction import QtCore + for name, cls in QtCore.__dict__.items(): - if isinstance(cls, type): globals()[name] = cls + if isinstance(cls, type): + globals()[name] = cls from .qt_abstraction import QtGui + for name, cls in QtGui.__dict__.items(): - if isinstance(cls, type): globals()[name] = cls + if isinstance(cls, type): + globals()[name] = cls from .recent_box import RecentBox from .aspect_preserving_label import AspectPreservingLabel -from . import resources_rc +from . import resources_rc + class Ui_LoginDialog(object): def setupUi(self, LoginDialog): if not LoginDialog.objectName(): - LoginDialog.setObjectName(u"LoginDialog") + LoginDialog.setObjectName("LoginDialog") LoginDialog.setWindowModality(Qt.NonModal) LoginDialog.resize(424, 304) LoginDialog.setMinimumSize(QSize(424, 304)) - LoginDialog.setStyleSheet(u"\n" -"QWidget\n" -"{\n" -" background-color: rgb(36, 39, 42);\n" -" color: rgb(192, 192, 192);\n" -" selection-background-color: rgb(168, 123, 43);\n" -" selection-color: rgb(230, 230, 230);\n" -" font-size: 11px;\n" -"}\n" -"\n" -"QPushButton\n" -"{\n" -" background-color: transparent;\n" -" border: 1px solid transparent;\n" -" border-radius: 2px;\n" -" padding: 8px;\n" -" padding-left: 15px;\n" -" padding-right: 15px;\n" -"}\n" -"\n" -"QPushButton::menu-indicator {\n" -" subcontrol-position: right center;\n" -"}\n" -"\n" -"QPushButton QMenu::item {\n" -" padding: 15px;\n" -" border: 1px solid transparent;\n" -"}\n" -"\n" -"QPushButton QMenu::item:disabled {\n" -" color: rgb(160, 160, 160);\n" -" font-style: italic;\n" -"}\n" -"\n" -"QPushButton QMenu::item:selected {\n" -" border-color: rgb(54, 60, 66);\n" -"}\n" -"\n" -"QPushButton QMenu::item:pressed\n" -"{\n" -" border-color: rgb(192, 192, 192);\n" -"}\n" -"\n" -"QLineEdit, QComboBox\n" -"{\n" -" background-color: rgb(29, 31, 34);\n" -" bord" - "er: 1px solid rgb(54, 60, 66);\n" -" border-radius: 2px;\n" -" padding: 5px;\n" -" font-size: 12px;\n" -"}\n" -"\n" -"QComboBox\n" -"{\n" -" margin-left: 3px;\n" -" margin-right: 3px;\n" -"}\n" -"\n" -"QPushButton:focus\n" -"{\n" -" border-color: rgb(48, 167, 227);\n" -" outline: none;\n" -"}\n" -"\n" -"QPushButton:hover {\n" -" border-color: rgb(54, 60, 66);\n" -"}\n" -"\n" -"QPushButton:pressed\n" -"{\n" -" border-color: rgb(192, 192, 192);\n" -"}\n" -"\n" -"QComboBox:focus, QLineEdit:focus\n" -"{\n" -" border: 1px solid rgb(48, 167, 227);\n" -"}\n" -"\n" -"QComboBox:drop-down:button {\n" -" border: 1px solid rgb(54, 60, 66);\n" -"}\n" -"\n" -"QComboBox:down-arrow {\n" -" image: url(:/shotgun_authentication/down-arrow.png);\n" -"}\n" -"\n" -"QLineEdit:disabled {\n" -" background-color: rgb(60, 60, 60);\n" -" color: rgb(160, 160, 160);\n" -"}\n" -"\n" -"QComboBox::drop-down:disabled {\n" -" border-width: 0px;\n" -"}\n" -"\n" -"QComboBox::down-arrow:disabled {\n" -" image: url(noimg); border-width: 0px;\n" -"}\n" -"" - "\n" -"QComboBox::disabled {\n" -" background-color: rgb(60, 60, 60);\n" -" color: rgb(160, 160, 160);\n" -"}\n" -"\n" -"QPushButton.main\n" -"{\n" -" background-color: rgb(35, 165, 225);\n" -" border-color: rgb(36, 39, 42);\n" -" color: rgb(248, 248, 248);\n" -"}\n" -"\n" -"QPushButton.main:focus, QPushButton.main:hover\n" -"{\n" -" border-color: rgb(54, 60, 66);\n" -"}\n" -"\n" -"QPushButton.main:pressed\n" -"{\n" -" border-color: rgb(248, 248, 248);\n" -"}\n" -"") + LoginDialog.setStyleSheet( + "\n" + "QWidget\n" + "{\n" + " background-color: rgb(36, 39, 42);\n" + " color: rgb(192, 192, 192);\n" + " selection-background-color: rgb(168, 123, 43);\n" + " selection-color: rgb(230, 230, 230);\n" + " font-size: 11px;\n" + "}\n" + "\n" + "QPushButton\n" + "{\n" + " background-color: transparent;\n" + " border: 1px solid transparent;\n" + " border-radius: 2px;\n" + " padding: 8px;\n" + " padding-left: 15px;\n" + " padding-right: 15px;\n" + "}\n" + "\n" + "QPushButton::menu-indicator {\n" + " subcontrol-position: right center;\n" + "}\n" + "\n" + "QPushButton QMenu::item {\n" + " padding: 15px;\n" + " border: 1px solid transparent;\n" + "}\n" + "\n" + "QPushButton QMenu::item:disabled {\n" + " color: rgb(160, 160, 160);\n" + " font-style: italic;\n" + "}\n" + "\n" + "QPushButton QMenu::item:selected {\n" + " border-color: rgb(54, 60, 66);\n" + "}\n" + "\n" + "QPushButton QMenu::item:pressed\n" + "{\n" + " border-color: rgb(192, 192, 192);\n" + "}\n" + "\n" + "QLineEdit, QComboBox\n" + "{\n" + " background-color: rgb(29, 31, 34);\n" + " bord" + "er: 1px solid rgb(54, 60, 66);\n" + " border-radius: 2px;\n" + " padding: 5px;\n" + " font-size: 12px;\n" + "}\n" + "\n" + "QComboBox\n" + "{\n" + " margin-left: 3px;\n" + " margin-right: 3px;\n" + "}\n" + "\n" + "QPushButton:focus\n" + "{\n" + " border-color: rgb(48, 167, 227);\n" + " outline: none;\n" + "}\n" + "\n" + "QPushButton:hover {\n" + " border-color: rgb(54, 60, 66);\n" + "}\n" + "\n" + "QPushButton:pressed\n" + "{\n" + " border-color: rgb(192, 192, 192);\n" + "}\n" + "\n" + "QComboBox:focus, QLineEdit:focus\n" + "{\n" + " border: 1px solid rgb(48, 167, 227);\n" + "}\n" + "\n" + "QComboBox:drop-down:button {\n" + " border: 1px solid rgb(54, 60, 66);\n" + "}\n" + "\n" + "QComboBox:down-arrow {\n" + " image: url(:/shotgun_authentication/down-arrow.png);\n" + "}\n" + "\n" + "QLineEdit:disabled {\n" + " background-color: rgb(60, 60, 60);\n" + " color: rgb(160, 160, 160);\n" + "}\n" + "\n" + "QComboBox::drop-down:disabled {\n" + " border-width: 0px;\n" + "}\n" + "\n" + "QComboBox::down-arrow:disabled {\n" + " image: url(noimg); border-width: 0px;\n" + "}\n" + "" + "\n" + "QComboBox::disabled {\n" + " background-color: rgb(60, 60, 60);\n" + " color: rgb(160, 160, 160);\n" + "}\n" + "\n" + "QPushButton.main\n" + "{\n" + " background-color: rgb(35, 165, 225);\n" + " border-color: rgb(36, 39, 42);\n" + " color: rgb(248, 248, 248);\n" + "}\n" + "\n" + "QPushButton.main:focus, QPushButton.main:hover\n" + "{\n" + " border-color: rgb(54, 60, 66);\n" + "}\n" + "\n" + "QPushButton.main:pressed\n" + "{\n" + " border-color: rgb(248, 248, 248);\n" + "}\n" + "" + ) LoginDialog.setModal(True) self.verticalLayout_2 = QVBoxLayout(LoginDialog) self.verticalLayout_2.setContentsMargins(20, 20, 20, 20) - self.verticalLayout_2.setObjectName(u"verticalLayout_2") + self.verticalLayout_2.setObjectName("verticalLayout_2") self.horizontalLayout = QHBoxLayout() self.horizontalLayout.setSpacing(0) - self.horizontalLayout.setObjectName(u"horizontalLayout") + self.horizontalLayout.setObjectName("horizontalLayout") self.horizontalLayout.setSizeConstraint(QLayout.SetMinAndMaxSize) self.logo = AspectPreservingLabel(LoginDialog) - self.logo.setObjectName(u"logo") + self.logo.setObjectName("logo") sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.logo.sizePolicy().hasHeightForWidth()) self.logo.setSizePolicy(sizePolicy) self.logo.setMaximumSize(QSize(320, 72)) - self.logo.setPixmap(QPixmap(u":/shotgun_authentication/shotgun_logo_light_medium.png")) + self.logo.setPixmap( + QPixmap(":/shotgun_authentication/shotgun_logo_light_medium.png") + ) self.logo.setAlignment(Qt.AlignCenter) self.logo.setTextInteractionFlags(Qt.NoTextInteraction) @@ -177,52 +186,54 @@ def setupUi(self, LoginDialog): self.verticalLayout_2.addLayout(self.horizontalLayout) self.stackedWidget = QStackedWidget(LoginDialog) - self.stackedWidget.setObjectName(u"stackedWidget") + self.stackedWidget.setObjectName("stackedWidget") self.stackedWidget.setMinimumSize(QSize(324, 172)) self.login_page = QWidget() - self.login_page.setObjectName(u"login_page") + self.login_page.setObjectName("login_page") self.verticalLayout_3 = QVBoxLayout(self.login_page) self.verticalLayout_3.setContentsMargins(0, 0, 0, 0) - self.verticalLayout_3.setObjectName(u"verticalLayout_3") + self.verticalLayout_3.setObjectName("verticalLayout_3") self.credentials = QWidget(self.login_page) - self.credentials.setObjectName(u"credentials") + self.credentials.setObjectName("credentials") self.credentials.setMinimumSize(QSize(0, 126)) self.verticalLayout_7 = QVBoxLayout(self.credentials) self.verticalLayout_7.setContentsMargins(0, 0, 0, 0) - self.verticalLayout_7.setObjectName(u"verticalLayout_7") + self.verticalLayout_7.setObjectName("verticalLayout_7") self.site = RecentBox(self.credentials) - self.site.setObjectName(u"site") + self.site.setObjectName("site") self.verticalLayout_7.addWidget(self.site) self.login = RecentBox(self.credentials) - self.login.setObjectName(u"login") + self.login.setObjectName("login") self.verticalLayout_7.addWidget(self.login) self.password = QLineEdit(self.credentials) - self.password.setObjectName(u"password") + self.password.setObjectName("password") self.password.setMinimumSize(QSize(308, 0)) self.password.setEchoMode(QLineEdit.Password) self.verticalLayout_7.addWidget(self.password) self.message = QLabel(self.credentials) - self.message.setObjectName(u"message") + self.message.setObjectName("message") sizePolicy1 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) sizePolicy1.setHorizontalStretch(0) sizePolicy1.setVerticalStretch(0) sizePolicy1.setHeightForWidth(self.message.sizePolicy().hasHeightForWidth()) self.message.setSizePolicy(sizePolicy1) self.message.setTextFormat(Qt.RichText) - self.message.setAlignment(Qt.AlignLeading|Qt.AlignLeft|Qt.AlignVCenter) + self.message.setAlignment(Qt.AlignLeading | Qt.AlignLeft | Qt.AlignVCenter) self.message.setWordWrap(True) self.message.setMargin(4) self.message.setOpenExternalLinks(True) self.verticalLayout_7.addWidget(self.message) - self.verticalSpacer_3 = QSpacerItem(20, 0, QSizePolicy.Minimum, QSizePolicy.Expanding) + self.verticalSpacer_3 = QSpacerItem( + 20, 0, QSizePolicy.Minimum, QSizePolicy.Expanding + ) self.verticalLayout_7.addItem(self.verticalSpacer_3) @@ -230,19 +241,19 @@ def setupUi(self, LoginDialog): self.button_layout = QHBoxLayout() self.button_layout.setSpacing(10) - self.button_layout.setObjectName(u"button_layout") + self.button_layout.setObjectName("button_layout") self.button_layout.setContentsMargins(0, -1, -1, -1) self.button_options = QPushButton(self.login_page) - self.button_options.setObjectName(u"button_options") + self.button_options.setObjectName("button_options") self.button_options.setAutoDefault(False) self.button_options.setFlat(True) self.button_layout.addWidget(self.button_options) self.links = QVBoxLayout() - self.links.setObjectName(u"links") + self.links.setObjectName("links") self.forgot_password_link = QLabel(self.login_page) - self.forgot_password_link.setObjectName(u"forgot_password_link") + self.forgot_password_link.setObjectName("forgot_password_link") self.forgot_password_link.setCursor(QCursor(Qt.PointingHandCursor)) self.forgot_password_link.setTextFormat(Qt.RichText) self.forgot_password_link.setMargin(4) @@ -252,12 +263,14 @@ def setupUi(self, LoginDialog): self.button_layout.addLayout(self.links) - self.sign_in_hspacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) + self.sign_in_hspacer = QSpacerItem( + 40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum + ) self.button_layout.addItem(self.sign_in_hspacer) self.sign_in = QPushButton(self.login_page) - self.sign_in.setObjectName(u"sign_in") + self.sign_in.setObjectName("sign_in") self.sign_in.setAutoDefault(True) self.sign_in.setFlat(True) @@ -269,54 +282,60 @@ def setupUi(self, LoginDialog): self.stackedWidget.addWidget(self.login_page) self._2fa_page = QWidget() - self._2fa_page.setObjectName(u"_2fa_page") + self._2fa_page.setObjectName("_2fa_page") self.verticalLayout_4 = QVBoxLayout(self._2fa_page) self.verticalLayout_4.setContentsMargins(0, 0, 0, 0) - self.verticalLayout_4.setObjectName(u"verticalLayout_4") + self.verticalLayout_4.setObjectName("verticalLayout_4") self.credentials_2 = QWidget(self._2fa_page) - self.credentials_2.setObjectName(u"credentials_2") + self.credentials_2.setObjectName("credentials_2") self.credentials_2.setMinimumSize(QSize(0, 133)) self.horizontalLayout_2 = QHBoxLayout(self.credentials_2) self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0) - self.horizontalLayout_2.setObjectName(u"horizontalLayout_2") + self.horizontalLayout_2.setObjectName("horizontalLayout_2") self.label = QLabel(self.credentials_2) - self.label.setObjectName(u"label") + self.label.setObjectName("label") self.label.setMinimumSize(QSize(86, 0)) - self.label.setPixmap(QPixmap(u":/google_authenticator/google_authenticator.png")) - self.label.setAlignment(Qt.AlignHCenter|Qt.AlignTop) + self.label.setPixmap(QPixmap(":/google_authenticator/google_authenticator.png")) + self.label.setAlignment(Qt.AlignHCenter | Qt.AlignTop) self.horizontalLayout_2.addWidget(self.label) self.widget_2 = QWidget(self.credentials_2) - self.widget_2.setObjectName(u"widget_2") + self.widget_2.setObjectName("widget_2") self.verticalLayout = QVBoxLayout(self.widget_2) - self.verticalLayout.setObjectName(u"verticalLayout") + self.verticalLayout.setObjectName("verticalLayout") self._2fa_message = QLabel(self.widget_2) - self._2fa_message.setObjectName(u"_2fa_message") + self._2fa_message.setObjectName("_2fa_message") sizePolicy2 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed) sizePolicy2.setHorizontalStretch(0) sizePolicy2.setVerticalStretch(0) - sizePolicy2.setHeightForWidth(self._2fa_message.sizePolicy().hasHeightForWidth()) + sizePolicy2.setHeightForWidth( + self._2fa_message.sizePolicy().hasHeightForWidth() + ) self._2fa_message.setSizePolicy(sizePolicy2) - self._2fa_message.setAlignment(Qt.AlignLeading|Qt.AlignLeft|Qt.AlignVCenter) + self._2fa_message.setAlignment(Qt.AlignLeading | Qt.AlignLeft | Qt.AlignVCenter) self._2fa_message.setWordWrap(True) self._2fa_message.setMargin(0) self.verticalLayout.addWidget(self._2fa_message) self._2fa_code = QLineEdit(self.widget_2) - self._2fa_code.setObjectName(u"_2fa_code") + self._2fa_code.setObjectName("_2fa_code") self.verticalLayout.addWidget(self._2fa_code) self.invalid_code = QLabel(self.widget_2) - self.invalid_code.setObjectName(u"invalid_code") - sizePolicy2.setHeightForWidth(self.invalid_code.sizePolicy().hasHeightForWidth()) + self.invalid_code.setObjectName("invalid_code") + sizePolicy2.setHeightForWidth( + self.invalid_code.sizePolicy().hasHeightForWidth() + ) self.invalid_code.setSizePolicy(sizePolicy2) self.verticalLayout.addWidget(self.invalid_code) - self.verticalSpacer_2 = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding) + self.verticalSpacer_2 = QSpacerItem( + 20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding + ) self.verticalLayout.addItem(self.verticalSpacer_2) @@ -326,20 +345,22 @@ def setupUi(self, LoginDialog): self.button_layout_2 = QHBoxLayout() self.button_layout_2.setSpacing(10) - self.button_layout_2.setObjectName(u"button_layout_2") + self.button_layout_2.setObjectName("button_layout_2") self.button_layout_2.setContentsMargins(0, -1, -1, -1) self.use_backup = QPushButton(self._2fa_page) - self.use_backup.setObjectName(u"use_backup") + self.use_backup.setObjectName("use_backup") self.use_backup.setFlat(True) self.button_layout_2.addWidget(self.use_backup) - self._2fa_hspacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) + self._2fa_hspacer = QSpacerItem( + 40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum + ) self.button_layout_2.addItem(self._2fa_hspacer) self.verify_2fa = QPushButton(self._2fa_page) - self.verify_2fa.setObjectName(u"verify_2fa") + self.verify_2fa.setObjectName("verify_2fa") self.verify_2fa.setMinimumSize(QSize(65, 0)) self.verify_2fa.setAutoDefault(False) self.verify_2fa.setFlat(True) @@ -350,50 +371,58 @@ def setupUi(self, LoginDialog): self.stackedWidget.addWidget(self._2fa_page) self.backup_page = QWidget() - self.backup_page.setObjectName(u"backup_page") + self.backup_page.setObjectName("backup_page") self.verticalLayout_6 = QVBoxLayout(self.backup_page) self.verticalLayout_6.setContentsMargins(0, 0, 0, 0) - self.verticalLayout_6.setObjectName(u"verticalLayout_6") + self.verticalLayout_6.setObjectName("verticalLayout_6") self.credentials_3 = QWidget(self.backup_page) - self.credentials_3.setObjectName(u"credentials_3") + self.credentials_3.setObjectName("credentials_3") self.credentials_3.setMinimumSize(QSize(0, 133)) self.horizontalLayout_3 = QHBoxLayout(self.credentials_3) self.horizontalLayout_3.setContentsMargins(0, 0, 0, 0) - self.horizontalLayout_3.setObjectName(u"horizontalLayout_3") + self.horizontalLayout_3.setObjectName("horizontalLayout_3") self.label_2 = QLabel(self.credentials_3) - self.label_2.setObjectName(u"label_2") - self.label_2.setPixmap(QPixmap(u":/backup_codes/backup_codes_light_bg.png")) - self.label_2.setAlignment(Qt.AlignHCenter|Qt.AlignTop) + self.label_2.setObjectName("label_2") + self.label_2.setPixmap(QPixmap(":/backup_codes/backup_codes_light_bg.png")) + self.label_2.setAlignment(Qt.AlignHCenter | Qt.AlignTop) self.horizontalLayout_3.addWidget(self.label_2) self.widget_4 = QWidget(self.credentials_3) - self.widget_4.setObjectName(u"widget_4") + self.widget_4.setObjectName("widget_4") self.verticalLayout_5 = QVBoxLayout(self.widget_4) - self.verticalLayout_5.setObjectName(u"verticalLayout_5") + self.verticalLayout_5.setObjectName("verticalLayout_5") self._2fa_message_2 = QLabel(self.widget_4) - self._2fa_message_2.setObjectName(u"_2fa_message_2") - sizePolicy2.setHeightForWidth(self._2fa_message_2.sizePolicy().hasHeightForWidth()) + self._2fa_message_2.setObjectName("_2fa_message_2") + sizePolicy2.setHeightForWidth( + self._2fa_message_2.sizePolicy().hasHeightForWidth() + ) self._2fa_message_2.setSizePolicy(sizePolicy2) - self._2fa_message_2.setAlignment(Qt.AlignLeading|Qt.AlignLeft|Qt.AlignVCenter) + self._2fa_message_2.setAlignment( + Qt.AlignLeading | Qt.AlignLeft | Qt.AlignVCenter + ) self._2fa_message_2.setWordWrap(True) self._2fa_message_2.setMargin(0) self.verticalLayout_5.addWidget(self._2fa_message_2) self.backup_code = QLineEdit(self.widget_4) - self.backup_code.setObjectName(u"backup_code") + self.backup_code.setObjectName("backup_code") self.verticalLayout_5.addWidget(self.backup_code) self.invalid_backup_code = QLabel(self.widget_4) - self.invalid_backup_code.setObjectName(u"invalid_backup_code") - sizePolicy2.setHeightForWidth(self.invalid_backup_code.sizePolicy().hasHeightForWidth()) + self.invalid_backup_code.setObjectName("invalid_backup_code") + sizePolicy2.setHeightForWidth( + self.invalid_backup_code.sizePolicy().hasHeightForWidth() + ) self.invalid_backup_code.setSizePolicy(sizePolicy2) self.verticalLayout_5.addWidget(self.invalid_backup_code) - self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding) + self.verticalSpacer = QSpacerItem( + 20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding + ) self.verticalLayout_5.addItem(self.verticalSpacer) @@ -403,21 +432,23 @@ def setupUi(self, LoginDialog): self.button_layout_3 = QHBoxLayout() self.button_layout_3.setSpacing(10) - self.button_layout_3.setObjectName(u"button_layout_3") + self.button_layout_3.setObjectName("button_layout_3") self.button_layout_3.setContentsMargins(0, -1, -1, -1) self.use_app = QPushButton(self.backup_page) - self.use_app.setObjectName(u"use_app") + self.use_app.setObjectName("use_app") self.use_app.setAutoDefault(False) self.use_app.setFlat(True) self.button_layout_3.addWidget(self.use_app) - self.backup_hspacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) + self.backup_hspacer = QSpacerItem( + 40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum + ) self.button_layout_3.addItem(self.backup_hspacer) self.verify_backup = QPushButton(self.backup_page) - self.verify_backup.setObjectName(u"verify_backup") + self.verify_backup.setObjectName("verify_backup") self.verify_backup.setMinimumSize(QSize(65, 0)) self.verify_backup.setAutoDefault(True) self.verify_backup.setFlat(True) @@ -428,33 +459,37 @@ def setupUi(self, LoginDialog): self.stackedWidget.addWidget(self.backup_page) self.asl_page = QWidget() - self.asl_page.setObjectName(u"asl_page") + self.asl_page.setObjectName("asl_page") self.verticalLayout_21 = QVBoxLayout(self.asl_page) self.verticalLayout_21.setContentsMargins(20, 20, 20, 20) - self.verticalLayout_21.setObjectName(u"verticalLayout_21") + self.verticalLayout_21.setObjectName("verticalLayout_21") self.asl_msg = QLabel(self.asl_page) - self.asl_msg.setObjectName(u"asl_msg") + self.asl_msg.setObjectName("asl_msg") sizePolicy1.setHeightForWidth(self.asl_msg.sizePolicy().hasHeightForWidth()) self.asl_msg.setSizePolicy(sizePolicy1) - self.asl_msg.setStyleSheet(u"padding-left: 40px; padding-left: 40px;padding-right: 40px;") + self.asl_msg.setStyleSheet( + "padding-left: 40px; padding-left: 40px;padding-right: 40px;" + ) self.asl_msg.setAlignment(Qt.AlignCenter) self.asl_msg.setWordWrap(True) self.verticalLayout_21.addWidget(self.asl_msg) self.asl_msg_back = QLabel(self.asl_page) - self.asl_msg_back.setObjectName(u"asl_msg_back") + self.asl_msg_back.setObjectName("asl_msg_back") self.asl_msg_back.setAlignment(Qt.AlignCenter) self.asl_msg_back.setWordWrap(True) self.verticalLayout_21.addWidget(self.asl_msg_back) - self.asl_spacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding) + self.asl_spacer = QSpacerItem( + 20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding + ) self.verticalLayout_21.addItem(self.asl_spacer) self.asl_msg_help = QLabel(self.asl_page) - self.asl_msg_help.setObjectName(u"asl_msg_help") + self.asl_msg_help.setObjectName("asl_msg_help") self.asl_msg_help.setAlignment(Qt.AlignCenter) self.asl_msg_help.setWordWrap(True) @@ -474,48 +509,124 @@ def setupUi(self, LoginDialog): self.verify_backup.setDefault(True) QMetaObject.connectSlotsByName(LoginDialog) + # setupUi def retranslateUi(self, LoginDialog): - LoginDialog.setWindowTitle(QCoreApplication.translate("LoginDialog", u"Flow Production Tracking Login", None)) + LoginDialog.setWindowTitle( + QCoreApplication.translate( + "LoginDialog", "Flow Production Tracking Login", None + ) + ) self.logo.setText("") -#if QT_CONFIG(accessibility) - self.site.setAccessibleName(QCoreApplication.translate("LoginDialog", u"site", None)) -#endif // QT_CONFIG(accessibility) -#if QT_CONFIG(accessibility) - self.login.setAccessibleName(QCoreApplication.translate("LoginDialog", u"login", None)) -#endif // QT_CONFIG(accessibility) -#if QT_CONFIG(accessibility) - self.password.setAccessibleName(QCoreApplication.translate("LoginDialog", u"password", None)) -#endif // QT_CONFIG(accessibility) - self.password.setPlaceholderText(QCoreApplication.translate("LoginDialog", u"password", None)) - self.message.setText(QCoreApplication.translate("LoginDialog", u"Please enter your credentials.", None)) - self.button_options.setText(QCoreApplication.translate("LoginDialog", u"See other options", None)) - self.forgot_password_link.setText(QCoreApplication.translate("LoginDialog", u"

Forgot your password?

", None)) - self.sign_in.setText(QCoreApplication.translate("LoginDialog", u"Sign In", None)) - self.sign_in.setProperty("class", QCoreApplication.translate("LoginDialog", u"main", None)) + # if QT_CONFIG(accessibility) + self.site.setAccessibleName( + QCoreApplication.translate("LoginDialog", "site", None) + ) + # endif // QT_CONFIG(accessibility) + # if QT_CONFIG(accessibility) + self.login.setAccessibleName( + QCoreApplication.translate("LoginDialog", "login", None) + ) + # endif // QT_CONFIG(accessibility) + # if QT_CONFIG(accessibility) + self.password.setAccessibleName( + QCoreApplication.translate("LoginDialog", "password", None) + ) + # endif // QT_CONFIG(accessibility) + self.password.setPlaceholderText( + QCoreApplication.translate("LoginDialog", "password", None) + ) + self.message.setText( + QCoreApplication.translate( + "LoginDialog", "Please enter your credentials.", None + ) + ) + self.button_options.setText( + QCoreApplication.translate("LoginDialog", "See other options", None) + ) + self.forgot_password_link.setText( + QCoreApplication.translate( + "LoginDialog", + '

Forgot your password?

', + None, + ) + ) + self.sign_in.setText(QCoreApplication.translate("LoginDialog", "Sign In", None)) + self.sign_in.setProperty( + "class", QCoreApplication.translate("LoginDialog", "main", None) + ) self.label.setText("") - self._2fa_message.setText(QCoreApplication.translate("LoginDialog", u"Enter the code generated by the Google Authenticator or Duo Mobile app.", None)) -#if QT_CONFIG(accessibility) - self._2fa_code.setAccessibleName(QCoreApplication.translate("LoginDialog", u"2fa code", None)) -#endif // QT_CONFIG(accessibility) - self._2fa_code.setPlaceholderText(QCoreApplication.translate("LoginDialog", u"Enter code", None)) + self._2fa_message.setText( + QCoreApplication.translate( + "LoginDialog", + "Enter the code generated by the Google Authenticator or Duo Mobile app.", + None, + ) + ) + # if QT_CONFIG(accessibility) + self._2fa_code.setAccessibleName( + QCoreApplication.translate("LoginDialog", "2fa code", None) + ) + # endif // QT_CONFIG(accessibility) + self._2fa_code.setPlaceholderText( + QCoreApplication.translate("LoginDialog", "Enter code", None) + ) self.invalid_code.setText("") - self.use_backup.setText(QCoreApplication.translate("LoginDialog", u"Use backup code", None)) - self.verify_2fa.setText(QCoreApplication.translate("LoginDialog", u"Verify", None)) - self.verify_2fa.setProperty("class", QCoreApplication.translate("LoginDialog", u"main", None)) + self.use_backup.setText( + QCoreApplication.translate("LoginDialog", "Use backup code", None) + ) + self.verify_2fa.setText( + QCoreApplication.translate("LoginDialog", "Verify", None) + ) + self.verify_2fa.setProperty( + "class", QCoreApplication.translate("LoginDialog", "main", None) + ) self.label_2.setText("") - self._2fa_message_2.setText(QCoreApplication.translate("LoginDialog", u"Please enter one of your backup codes.", None)) -#if QT_CONFIG(accessibility) - self.backup_code.setAccessibleName(QCoreApplication.translate("LoginDialog", u"backup code", None)) -#endif // QT_CONFIG(accessibility) + self._2fa_message_2.setText( + QCoreApplication.translate( + "LoginDialog", "Please enter one of your backup codes.", None + ) + ) + # if QT_CONFIG(accessibility) + self.backup_code.setAccessibleName( + QCoreApplication.translate("LoginDialog", "backup code", None) + ) + # endif // QT_CONFIG(accessibility) self.backup_code.setText("") - self.backup_code.setPlaceholderText(QCoreApplication.translate("LoginDialog", u"Enter backup code", None)) + self.backup_code.setPlaceholderText( + QCoreApplication.translate("LoginDialog", "Enter backup code", None) + ) self.invalid_backup_code.setText("") - self.use_app.setText(QCoreApplication.translate("LoginDialog", u"Use Google App", None)) - self.verify_backup.setText(QCoreApplication.translate("LoginDialog", u"Verify", None)) - self.verify_backup.setProperty("class", QCoreApplication.translate("LoginDialog", u"main", None)) - self.asl_msg.setText(QCoreApplication.translate("LoginDialog", u"Check your default web browser to continue logging in.", None)) - self.asl_msg_back.setText(QCoreApplication.translate("LoginDialog", u"

Cancel & return to the login page

", None)) - self.asl_msg_help.setText(QCoreApplication.translate("LoginDialog", u"

If you are having trouble logging in with the browser, select this support link

", None)) + self.use_app.setText( + QCoreApplication.translate("LoginDialog", "Use Google App", None) + ) + self.verify_backup.setText( + QCoreApplication.translate("LoginDialog", "Verify", None) + ) + self.verify_backup.setProperty( + "class", QCoreApplication.translate("LoginDialog", "main", None) + ) + self.asl_msg.setText( + QCoreApplication.translate( + "LoginDialog", + "Check your default web browser to continue logging in.", + None, + ) + ) + self.asl_msg_back.setText( + QCoreApplication.translate( + "LoginDialog", + '

Cancel & return to the login page

', + None, + ) + ) + self.asl_msg_help.setText( + QCoreApplication.translate( + "LoginDialog", + '

If you are having trouble logging in with the browser, select this support link

', + None, + ) + ) + # retranslateUi diff --git a/python/tk-core/python/tank/authentication/ui/resources_rc.py b/python/tk-core/python/tank/authentication/ui/resources_rc.py index 95c08531..8163a110 100644 --- a/python/tk-core/python/tank/authentication/ui/resources_rc.py +++ b/python/tk-core/python/tank/authentication/ui/resources_rc.py @@ -3715,10 +3715,17 @@ \x00\x00\x01\x8f05\xd1\xf5\ " + def qInitResources(): - QtCore.qRegisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) + QtCore.qRegisterResourceData( + 0x03, qt_resource_struct, qt_resource_name, qt_resource_data + ) + def qCleanupResources(): - QtCore.qUnregisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) + QtCore.qUnregisterResourceData( + 0x03, qt_resource_struct, qt_resource_name, qt_resource_data + ) + qInitResources() diff --git a/python/tk-core/python/tank/authentication/ui_authentication.py b/python/tk-core/python/tank/authentication/ui_authentication.py index 486262e6..8158e2c1 100644 --- a/python/tk-core/python/tank/authentication/ui_authentication.py +++ b/python/tk-core/python/tank/authentication/ui_authentication.py @@ -18,9 +18,9 @@ -------------------------------------------------------------------------------- """ -from .errors import AuthenticationCancelled, ShotgunAuthenticationError -from . import invoker from .. import LogManager +from . import invoker +from .errors import AuthenticationCancelled, ShotgunAuthenticationError logger = LogManager.get_logger(__name__) diff --git a/python/tk-core/python/tank/authentication/user.py b/python/tk-core/python/tank/authentication/user.py index 37c00350..56d20846 100644 --- a/python/tk-core/python/tank/authentication/user.py +++ b/python/tk-core/python/tank/authentication/user.py @@ -12,13 +12,10 @@ import threading import time -from . import sso_saml2 -from . import interactive_authentication -from . import user_impl from .. import LogManager +from . import interactive_authentication, sso_saml2, user_impl from .errors import AuthenticationCancelled - logger = LogManager.get_logger(__name__) # Ensure that the SSO-related logging will be merged in our loggin. diff --git a/python/tk-core/python/tank/authentication/user_impl.py b/python/tk-core/python/tank/authentication/user_impl.py index 3cd32e60..7d434043 100644 --- a/python/tk-core/python/tank/authentication/user_impl.py +++ b/python/tk-core/python/tank/authentication/user_impl.py @@ -18,16 +18,17 @@ at any point. -------------------------------------------------------------------------------- """ -import json + import http.client +import json -from .shotgun_wrapper import ShotgunWrapper -from tank_vendor.shotgun_api3 import Shotgun, AuthenticationFault, ProtocolError +from tank_vendor.shotgun_api3 import AuthenticationFault, ProtocolError, Shotgun -from . import session_cache -from .errors import IncompleteCredentials, UnresolvableHumanUser, UnresolvableScriptUser from .. import LogManager from ..util import pickle +from . import session_cache +from .errors import IncompleteCredentials, UnresolvableHumanUser, UnresolvableScriptUser +from .shotgun_wrapper import ShotgunWrapper # Indirection to create ShotgunWrapper instances. Great for unit testing. _shotgun_instance_factory = ShotgunWrapper @@ -606,7 +607,6 @@ def deserialize_user(payload): user_dict = pickle.loads(payload) # Find which user type we have - global __factories factory = __factories.get(user_dict.get("type")) # Unknown type, something is wrong. Maybe backward compatible code broke? if not factory: diff --git a/python/tk-core/python/tank/authentication/web_login_support.py b/python/tk-core/python/tank/authentication/web_login_support.py index a3a93e4a..21abf161 100644 --- a/python/tk-core/python/tank/authentication/web_login_support.py +++ b/python/tk-core/python/tank/authentication/web_login_support.py @@ -59,5 +59,4 @@ def get_shotgun_authenticator_support_web_login(): :returns: Bool indicating support for the Unified Login Flow. """ - global shotgun_authenticator_support_web_login return shotgun_authenticator_support_web_login diff --git a/python/tk-core/python/tank/bootstrap/__init__.py b/python/tk-core/python/tank/bootstrap/__init__.py index ada6fd37..e3edeaa0 100644 --- a/python/tk-core/python/tank/bootstrap/__init__.py +++ b/python/tk-core/python/tank/bootstrap/__init__.py @@ -8,5 +8,5 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -from .manager import ToolkitManager from .errors import TankBootstrapError, TankMissingTankNameError +from .manager import ToolkitManager diff --git a/python/tk-core/python/tank/bootstrap/baked_configuration.py b/python/tk-core/python/tank/bootstrap/baked_configuration.py index 21b2d81b..7ec5c64c 100644 --- a/python/tk-core/python/tank/bootstrap/baked_configuration.py +++ b/python/tk-core/python/tank/bootstrap/baked_configuration.py @@ -10,16 +10,12 @@ import os -from .configuration import Configuration -from .configuration_writer import ConfigurationWriter - -from .. import LogManager -from .. import constants - +from .. import LogManager, constants, pipelineconfig_utils +from ..errors import TankFileDoesNotExistError from ..util import ShotgunPath from ..util.pickle import store_env_var_pickled -from ..errors import TankFileDoesNotExistError -from .. import pipelineconfig_utils +from .configuration import Configuration +from .configuration_writer import ConfigurationWriter log = LogManager.get_logger(__name__) diff --git a/python/tk-core/python/tank/bootstrap/bundle_downloader.py b/python/tk-core/python/tank/bootstrap/bundle_downloader.py index 42a391fc..8a5c955a 100644 --- a/python/tk-core/python/tank/bootstrap/bundle_downloader.py +++ b/python/tk-core/python/tank/bootstrap/bundle_downloader.py @@ -9,8 +9,8 @@ # not expressly granted therein are reserved by Shotgun Software Inc. import os -from sgtk import hook -from sgtk import LogManager + +from sgtk import LogManager, hook log = LogManager.get_logger(__name__) diff --git a/python/tk-core/python/tank/bootstrap/cached_configuration.py b/python/tk-core/python/tank/bootstrap/cached_configuration.py index a8425398..2dc41623 100644 --- a/python/tk-core/python/tank/bootstrap/cached_configuration.py +++ b/python/tk-core/python/tank/bootstrap/cached_configuration.py @@ -9,21 +9,18 @@ # not expressly granted therein are reserved by Shotgun Software Inc. import os -import sys -import traceback import pprint - -from . import constants - -from ..descriptor import create_descriptor, Descriptor -from .errors import TankBootstrapError, TankMissingTankNameError - -from ..util import filesystem, version +import traceback from tank_vendor import yaml + +from .. import LogManager +from ..descriptor import Descriptor, create_descriptor +from ..util import filesystem +from . import constants from .configuration import Configuration from .configuration_writer import ConfigurationWriter -from .. import LogManager +from .errors import TankBootstrapError, TankMissingTankNameError log = LogManager.get_logger(__name__) @@ -140,7 +137,7 @@ def verify_required_shotgun_fields(self): % (storage_roots.roots_file, storage_roots.required_roots) ) - (_, unmapped_roots) = storage_roots.get_local_storages(self._sg_connection) + _, unmapped_roots = storage_roots.get_local_storages(self._sg_connection) # get a list of all defined storage roots without a corresponding PTR # local storage defined @@ -258,10 +255,10 @@ def update_configuration(self): try: # Move to backup needs to undo changes when failing because we need to put the configuration # in a usable state. - (config_backup_path, core_backup_path) = self._config_writer.move_to_backup( + config_backup_path, core_backup_path = self._config_writer.move_to_backup( undo_on_error=True ) - except Exception as e: + except Exception: log.exception( "Unexpected error while making a backup of the configuration. Toolkit will use the " "original configuration." diff --git a/python/tk-core/python/tank/bootstrap/configuration.py b/python/tk-core/python/tank/bootstrap/configuration.py index cb58e162..8c0debdb 100644 --- a/python/tk-core/python/tank/bootstrap/configuration.py +++ b/python/tk-core/python/tank/bootstrap/configuration.py @@ -11,16 +11,14 @@ import inspect import os -from .import_handler import CoreImportHandler - -from ..log import LogManager -from .. import pipelineconfig_utils -from .. import constants +from .. import constants, pipelineconfig_utils from ..authentication import ( + ShotgunSamlUser, get_shotgun_authenticator_support_web_login, serialize_user, - ShotgunSamlUser, ) +from ..log import LogManager +from .import_handler import CoreImportHandler log = LogManager.get_logger(__name__) @@ -178,8 +176,7 @@ def _tank_from_path(self, path): """ # Perform a local import here to make sure we are getting # the newly swapped in core code, if it was swapped - from .. import api - from .. import pipelineconfig + from .. import api, pipelineconfig log.debug("Executing tank_from_path('%s')" % path) @@ -267,6 +264,7 @@ def _set_authenticated_user( ShotgunAuthenticator, deserialize_user, ) + from ..util import CoreDefaultsManager except ImportError: log.debug("Using pre-0.16 core, no authenticated user will be set.") diff --git a/python/tk-core/python/tank/bootstrap/configuration_writer.py b/python/tk-core/python/tank/bootstrap/configuration_writer.py index 3e95a306..17f33aa4 100644 --- a/python/tk-core/python/tank/bootstrap/configuration_writer.py +++ b/python/tk-core/python/tank/bootstrap/configuration_writer.py @@ -8,23 +8,17 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. +import datetime import os import sys -import datetime - -from . import constants - -from ..descriptor import Descriptor, create_descriptor, is_descriptor_version_missing - -from ..util import filesystem -from ..util import StorageRoots -from ..util.shotgun import connection -from ..util.move_guard import MoveGuard -from ..util import is_macos, is_windows from tank_vendor import yaml from .. import LogManager +from ..util import StorageRoots, filesystem, is_macos, is_windows +from ..util.move_guard import MoveGuard +from ..util.shotgun import connection +from . import constants log = LogManager.get_logger(__name__) @@ -265,7 +259,9 @@ def create_tank_command(self, executable=sys.executable, prefix=sys.prefix): if current_interpreter: log.debug("Current OS interpreter will be %s.", current_interpreter) else: - log.debug("Current OS interpreter will be the default PTR desktop app location.") + log.debug( + "Current OS interpreter will be the default PTR desktop app location." + ) config_root_path = self._path.current_os @@ -339,7 +335,9 @@ def write_config_info_file(self, config_descriptor): with filesystem.auto_created_yml(config_info_file) as fh: fh.write("# This file contains metadata describing what exact version\n") - fh.write("# Of the config that was downloaded from Flow Production Tracking\n") + fh.write( + "# Of the config that was downloaded from Flow Production Tracking\n" + ) fh.write("\n") fh.write("# Below follows details for the sg attachment that is\n") fh.write("# reflected within this local configuration.\n") diff --git a/python/tk-core/python/tank/bootstrap/import_handler.py b/python/tk-core/python/tank/bootstrap/import_handler.py index 715d61e1..2e16ad25 100644 --- a/python/tk-core/python/tank/bootstrap/import_handler.py +++ b/python/tk-core/python/tank/bootstrap/import_handler.py @@ -102,7 +102,7 @@ def swap_core(cls, core_path): try: if prev_log_file: tank.LogManager().initialize_base_file_handler_from_path(prev_log_file) - except AttributeError as e: + except AttributeError: # older versions of the API may not have this defined. log.debug( "Switching to a version of the core API that doesn't " @@ -289,20 +289,14 @@ def find_spec(self, module_fullname, package_path=None, target=None): # later raise FileNotFoundError instead of the expected ImportError # when the module doesn't exist on disk. if os.path.isdir(os.path.join(package_path[0], module_name)): - module_file = os.path.join( - package_path[0], module_name, "__init__.py" - ) + module_file = os.path.join(package_path[0], module_name, "__init__.py") else: - module_file = os.path.join( - package_path[0], module_name + ".py" - ) + module_file = os.path.join(package_path[0], module_name + ".py") if not os.path.isfile(module_file): return None - loader = importlib.machinery.SourceFileLoader( - module_fullname, module_file - ) + loader = importlib.machinery.SourceFileLoader(module_fullname, module_file) spec = importlib.util.spec_from_loader(loader.name, loader) self._module_info[module_fullname] = spec except ImportError: @@ -328,9 +322,7 @@ def load_module(self, module_fullname): try: spec.loader.exec_module(module) except FileNotFoundError as e: - raise ImportError( - f"No module named '{module_fullname}'" - ) from e + raise ImportError(f"No module named '{module_fullname}'") from e # the module needs to know the loader so that reload() works module.__loader__ = self diff --git a/python/tk-core/python/tank/bootstrap/installed_configuration.py b/python/tk-core/python/tank/bootstrap/installed_configuration.py index fa5f5334..4de4ffd7 100644 --- a/python/tk-core/python/tank/bootstrap/installed_configuration.py +++ b/python/tk-core/python/tank/bootstrap/installed_configuration.py @@ -10,9 +10,9 @@ import os -from .errors import TankBootstrapError -from .configuration import Configuration from .. import LogManager +from .configuration import Configuration +from .errors import TankBootstrapError log = LogManager.get_logger(__name__) diff --git a/python/tk-core/python/tank/bootstrap/manager.py b/python/tk-core/python/tank/bootstrap/manager.py index b058b6e8..7efc505d 100644 --- a/python/tk-core/python/tank/bootstrap/manager.py +++ b/python/tk-core/python/tank/bootstrap/manager.py @@ -8,18 +8,20 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -import os import inspect +import os -from . import constants -from .errors import TankBootstrapError -from .configuration import Configuration -from .resolver import ConfigurationResolver -from ..authentication import ShotgunAuthenticator -from ..pipelineconfig import PipelineConfiguration from .. import LogManager +from ..authentication import ShotgunAuthenticator, flow_auth from ..errors import TankError +from ..flowam import constants as flow_const +from ..flowam import utils as flow_utils +from ..pipelineconfig import PipelineConfiguration from ..util import ShotgunPath +from . import constants +from .configuration import Configuration +from .errors import TankBootstrapError +from .resolver import ConfigurationResolver log = LogManager.get_logger(__name__) @@ -33,12 +35,12 @@ class ToolkitManager(object): # Constants used to make the manager bootstrapping: # - download and cache the config dependencies needed to run the engine being started in a specific environment. # - download and cache all the config dependencies needed to run the engine in any environment. - (CACHE_SPARSE, CACHE_FULL) = range(2) + CACHE_SPARSE, CACHE_FULL = range(2) # Constants used to indicate that the manager is: # - bootstrapping the toolkit (with method bootstrap_toolkit), # - starting up the engine (with method _start_engine). - (TOOLKIT_BOOTSTRAP_PHASE, ENGINE_STARTUP_PHASE) = range(2) + TOOLKIT_BOOTSTRAP_PHASE, ENGINE_STARTUP_PHASE = range(2) # List of constants representing the status of the progress bar when these event occurs during bootstrap. _RESOLVING_PROJECT_RATE = 0.0 @@ -84,6 +86,10 @@ def __init__(self, sg_user=None): self._plugin_id = None self._allow_config_overrides = True + # flow fields + self._flow_project_id = None + self._flow_schema_version = None + # look for the standard env var SHOTGUN_PIPELINE_CONFIGURATION_ID # and in case this is set, use it as a default if constants.PIPELINE_CONFIG_ID_ENV_VAR in os.environ: @@ -914,6 +920,84 @@ def _log_startup_message(self, engine_name, entity): log.debug("Bootstrapping engine %s." % engine_name) log.debug("-----------------------------------------------------------------") + def _resolve_project_id(self, entity): + """ + Resolve a Shotgun project id from a target entity. + + :param entity: ``None``, a Project dict, an entity with a ``project`` + link, or any entity that can be looked up in Shotgun + to find its parent project. + :type entity: dict or None + :returns: Integer project id, or ``None`` for the site context. + :rtype: int or None + """ + if entity is None: + return None + + if entity.get("type") == "Project": + return entity["id"] + + if "project" in entity and entity["project"].get("type") == "Project": + return entity["project"]["id"] + + data = self._sg_connection.find_one( + entity["type"], [["id", "is", entity["id"]]], ["project"] + ) + if not data or not data.get("project"): + raise TankBootstrapError("Cannot resolve project for %s" % entity) + return data["project"]["id"] + + def _trigger_am_auth(self, pipeline_config, entity, progress_callback): + """ + Proactively obtain a Flow/MEDM access token. + Silent path (file store -> refresh) is tried first; falls + back to opening a browser for PKCE if no usable cached/refresh token + exists. + + Configuration errors raise ``TankBootstrapError`` (deployment bug). + Runtime auth failures are logged and swallowed unless the + ``TK_FLOW_AUTH_REQUIRED`` env var is set to ``"1"``, in which case + they raise ``TankBootstrapError``. + + :param pipeline_config: PipelineConfiguration object. + :param entity: Shotgun entity used to resolve a project context. + :type entity: dict or None + :param progress_callback: Callback function that reports back on the toolkit bootstrap progress. + Set to ``None`` to use the default callback function. + :rtype: None + """ + if entity is None: + return + + try: + log.info("Triggering Flow authentication...") + overrides = flow_utils.get_config_flow_settings(pipeline_config) + # Set authentication overrides in reserved env vars so they will persist + # across toolkit engine sessions + if flow_const.FLOW_AUTH_APP_ID in overrides: + os.environ["TK_FLOW_AUTH_APPLICATION_ID"] = overrides[ + flow_const.FLOW_AUTH_APP_ID + ] + if flow_const.FLOW_AUTH_BASE_URL in overrides: + os.environ["TK_FLOW_AUTH_BASE_URL"] = overrides[ + flow_const.FLOW_AUTH_BASE_URL + ] + if flow_const.FLOW_AUTH_CALLBACK_URL in overrides: + os.environ["TK_FLOW_AUTH_CALLBACK_URL"] = overrides[ + flow_const.FLOW_AUTH_CALLBACK_URL + ] + settings = flow_auth.resolve_flow_auth_settings() + flow_auth.init_authentication(settings) + # Token is intentionally discarded here; it now sits in the file + # store and adsk_auth's in-memory cache for the next consumer. + flow_auth.get_access_token() + except flow_auth.FlowAuthConfigurationError as e: + raise TankBootstrapError( + "MEDM auth misconfigured for AM-ready project: %s" % e + ) + except Exception as e: + raise TankBootstrapError("MEDM auth failed for AM-ready project: %s" % e) + def _get_configuration(self, entity, progress_callback): """ Resolves the configuration to use without creating it on disk. @@ -928,25 +1012,7 @@ def _get_configuration(self, entity, progress_callback): self._report_progress( progress_callback, self._RESOLVING_PROJECT_RATE, "Resolving project..." ) - if entity is None: - project_id = None - - elif entity.get("type") == "Project": - project_id = entity["id"] - - elif "project" in entity and entity["project"].get("type") == "Project": - # user passed a project link - project_id = entity["project"]["id"] - - else: - # resolve from shotgun - data = self._sg_connection.find_one( - entity["type"], [["id", "is", entity["id"]]], ["project"] - ) - - if not data or not data.get("project"): - raise TankBootstrapError("Cannot resolve project for %s" % entity) - project_id = data["project"]["id"] + project_id = self._resolve_project_id(entity) # get an object to represent the business logic for # how a configuration location is being determined @@ -997,7 +1063,9 @@ def _get_configuration(self, entity, progress_callback): elif self._do_shotgun_config_lookup: # do the full resolve where we connect to shotgun etc. - log.debug("Checking for pipeline configuration overrides in Flow Production Tracking.") + log.debug( + "Checking for pipeline configuration overrides in Flow Production Tracking." + ) log.debug( "In order to turn this off, set do_shotgun_config_lookup to False" ) @@ -1063,6 +1131,36 @@ def _get_updated_configuration(self, entity, progress_callback): else: raise TankBootstrapError("Unknown configuration update status!") + if entity is not None: + project_id = self._resolve_project_id(entity) + if project_id: + sg_project = self._sg_connection.find_one( + "Project", + [["id", "is", project_id]], + [ + flow_auth.AM_READY_PROJECT_FIELD, + flow_const.FLOW_SCHEMA_VERSION_FIELD, + ], + ) + if sg_project and sg_project.get(flow_auth.AM_READY_PROJECT_FIELD): + # Store flow fields temporarily to avoid re-querying. + # They will be cached on the context object after the context object has been rebuilt. + self._flow_project_id = sg_project.get( + flow_auth.AM_READY_PROJECT_FIELD + ) + self._flow_schema_version = sg_project.get( + flow_const.FLOW_SCHEMA_VERSION_FIELD + ) + log.info( + f"Current SG project is associated with a Flow project: {self._flow_project_id} with schema version {self._flow_schema_version}." + ) + + tk, _ = config.get_tk_instance(self._sg_user) + # Authenticate into Flow AM + self._trigger_am_auth( + tk.pipeline_configuration, entity, progress_callback + ) + return config def _bootstrap_sgtk(self, engine_name, entity, progress_callback=None): @@ -1114,6 +1212,11 @@ def _start_engine(self, tk, engine_name, entity, progress_callback=None): If entity is None, the method will bootstrap into the site config. + For Flow-enabled projects, the Flow project id and schema version + cached during ``_get_updated_configuration()`` are injected onto + ``ctx.project`` so the engine can read them via + ``context.flow_project_id`` and ``context.flow_schema_version``. + Please note that the API version of the tk instance that hosts the engine may not be the same as the API version that was executed during the bootstrap. @@ -1140,6 +1243,13 @@ def _start_engine(self, tk, engine_name, entity, progress_callback=None): else: ctx = tk.context_from_entity_dictionary(entity) + # Inject flow fields to context if current project is related to a Flow project. + if ctx.project and self._flow_project_id is not None: + ctx.project[flow_auth.AM_READY_PROJECT_FIELD] = self._flow_project_id + ctx.project[flow_const.FLOW_SCHEMA_VERSION_FIELD] = ( + self._flow_schema_version + ) + self._report_progress( progress_callback, self._LAUNCHING_ENGINE_RATE, "Launching Engine..." ) diff --git a/python/tk-core/python/tank/bootstrap/resolver.py b/python/tk-core/python/tank/bootstrap/resolver.py index d7f19204..e4990373 100644 --- a/python/tk-core/python/tank/bootstrap/resolver.py +++ b/python/tk-core/python/tank/bootstrap/resolver.py @@ -13,27 +13,25 @@ on disk. """ -import sys -import os import fnmatch +import os import pprint +import sys +from .. import LogManager from ..descriptor import ( Descriptor, create_descriptor, descriptor_uri_to_dict, is_descriptor_version_missing, ) -from .errors import TankBootstrapError, TankBootstrapInvalidPipelineConfigurationError +from ..descriptor.descriptor_installed_config import InstalledConfigDescriptor +from ..util import LocalFileStorageManager, ShotgunPath, filesystem +from . import constants from .baked_configuration import BakedConfiguration from .cached_configuration import CachedConfiguration +from .errors import TankBootstrapError, TankBootstrapInvalidPipelineConfigurationError from .installed_configuration import InstalledConfiguration -from ..descriptor.descriptor_installed_config import InstalledConfigDescriptor -from ..util import filesystem -from ..util import ShotgunPath -from ..util import LocalFileStorageManager -from .. import LogManager -from . import constants log = LogManager.get_logger(__name__) @@ -376,9 +374,9 @@ def _get_pipeline_configurations_for_project( # field. Note that this may be None if for example the pipeline configuration # is defined for another operating system. try: - pipeline_config[ - "config_descriptor" - ] = self._create_config_descriptor(sg_connection, pipeline_config) + pipeline_config["config_descriptor"] = ( + self._create_config_descriptor(sg_connection, pipeline_config) + ) yield pipeline_config except TankBootstrapInvalidPipelineConfigurationError as e: @@ -841,7 +839,9 @@ def resolve_shotgun_configuration( "Will use pipeline configuration id '%s'" % pipeline_config_identifier ) - log.debug("Requesting pipeline configuration data from Flow Production Tracking...") + log.debug( + "Requesting pipeline configuration data from Flow Production Tracking..." + ) # Fetch the one and only config that matches this id. pipeline_config = sg_connection.find_one( @@ -869,7 +869,9 @@ def resolve_shotgun_configuration( # We couldn't resolve anything from Shotgun, so we'll resolve the configuration using # an offline resolve. - return self.resolve_not_found_sg_configuration(fallback_config_descriptor, sg_connection) + return self.resolve_not_found_sg_configuration( + fallback_config_descriptor, sg_connection + ) else: # Something was found in Shotgun, which means we've also potentially resolved its diff --git a/python/tk-core/python/tank/commands/__init__.py b/python/tk-core/python/tank/commands/__init__.py index 3df351e8..e63da80e 100644 --- a/python/tk-core/python/tank/commands/__init__.py +++ b/python/tk-core/python/tank/commands/__init__.py @@ -8,5 +8,5 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -from .tank_command import list_commands, get_command, SgtkSystemCommand from .interaction import CommandInteraction +from .tank_command import SgtkSystemCommand, get_command, list_commands diff --git a/python/tk-core/python/tank/commands/app_info.py b/python/tk-core/python/tank/commands/app_info.py index 03de1802..2f1c214d 100644 --- a/python/tk-core/python/tank/commands/app_info.py +++ b/python/tk-core/python/tank/commands/app_info.py @@ -8,8 +8,8 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -from .action_base import Action from ..errors import TankError +from .action_base import Action class AppInfoAction(Action): @@ -108,7 +108,7 @@ def _env_breakdown(self, log, env_name): descriptor = env.get_app_descriptor(eng, app) log.info("App %s" % app) log.info("-" * (4 + len(app))) - for (k, v) in descriptor.get_dict().items(): + for k, v in descriptor.get_dict().items(): log.info(" %s: %s" % (k.capitalize(), v)) log.info(" Docs: %s" % descriptor.documentation_url) log.info("") diff --git a/python/tk-core/python/tank/commands/cache_apps.py b/python/tk-core/python/tank/commands/cache_apps.py index d6dc0d04..30ecaf35 100644 --- a/python/tk-core/python/tank/commands/cache_apps.py +++ b/python/tk-core/python/tank/commands/cache_apps.py @@ -8,8 +8,8 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -from .action_base import Action from ..errors import TankError +from .action_base import Action class CacheAppsAction(Action): diff --git a/python/tk-core/python/tank/commands/cache_yaml.py b/python/tk-core/python/tank/commands/cache_yaml.py index b299f4c0..3628aab4 100644 --- a/python/tk-core/python/tank/commands/cache_yaml.py +++ b/python/tk-core/python/tank/commands/cache_yaml.py @@ -8,13 +8,12 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -import os import fnmatch +import os - -from .action_base import Action from ..errors import TankError -from ..util import yaml_cache, pickle +from ..util import pickle, yaml_cache +from .action_base import Action class CacheYamlAction(Action): diff --git a/python/tk-core/python/tank/commands/clone_configuration.py b/python/tk-core/python/tank/commands/clone_configuration.py index bed03b0d..f1115dc1 100644 --- a/python/tk-core/python/tank/commands/clone_configuration.py +++ b/python/tk-core/python/tank/commands/clone_configuration.py @@ -8,19 +8,16 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -from ..util import ShotgunPath -from ..errors import TankError -from . import constants -from ..util import filesystem -from ..util import is_linux, is_macos, is_windows +import os +import shutil from tank_vendor import yaml +from ..errors import TankError +from ..util import ShotgunPath, filesystem, is_linux, is_macos, is_windows +from . import constants from .action_base import Action -import os -import shutil - class CloneConfigAction(Action): """ diff --git a/python/tk-core/python/tank/commands/console_utils.py b/python/tk-core/python/tank/commands/console_utils.py index b8838566..5d4f21ea 100644 --- a/python/tk-core/python/tank/commands/console_utils.py +++ b/python/tk-core/python/tank/commands/console_utils.py @@ -15,12 +15,11 @@ import textwrap from .. import pipelineconfig_utils -from ..platform import validation -from ..errors import TankError, TankNoDefaultValueError from ..descriptor import CheckVersionConstraintsError +from ..errors import TankError, TankNoDefaultValueError +from ..platform import validation from ..platform.bundle import resolve_default_value - ########################################################################################## # displaying of info in the terminal, ascii-graphcics style @@ -34,7 +33,7 @@ def format_bundle_info(log, descriptor, required_updates=None): :param required_updates: A list of bundle names that require updating. """ # yay we can install! - get release notes - (summary, url) = descriptor.changelog + summary, url = descriptor.changelog if required_updates: add_padding = " " diff --git a/python/tk-core/python/tank/commands/core_localize.py b/python/tk-core/python/tank/commands/core_localize.py index f29f1385..0eb6b1bd 100644 --- a/python/tk-core/python/tank/commands/core_localize.py +++ b/python/tk-core/python/tank/commands/core_localize.py @@ -8,17 +8,16 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. +import datetime import os -import sys import shutil -import datetime +import sys +from .. import pipelineconfig_factory, pipelineconfig_utils from ..errors import TankError from ..util import filesystem from ..util.version import is_version_older from .action_base import Action -from .. import pipelineconfig_utils -from .. import pipelineconfig_factory # these are the items that need to be copied across # when a configuration is upgraded to contain a core API diff --git a/python/tk-core/python/tank/commands/core_upgrade.py b/python/tk-core/python/tank/commands/core_upgrade.py index 183044c9..0516f2b7 100644 --- a/python/tk-core/python/tank/commands/core_upgrade.py +++ b/python/tk-core/python/tank/commands/core_upgrade.py @@ -11,22 +11,21 @@ """ Tank command allowing to do core updates. """ -from ..errors import TankError -from .action_base import Action +import copy +import optparse import os import sys import textwrap -import optparse -import copy - -from ..util import shotgun -from .. import pipelineconfig_utils -from . import console_utils -from ..util.version import is_version_newer, is_version_head from tank_vendor import yaml +from .. import pipelineconfig_utils +from ..errors import TankError +from ..util import shotgun +from ..util.version import is_version_head, is_version_newer +from .action_base import Action + # FIXME: This should be refactored into something that can be used by other commands. class TkOptParse(optparse.OptionParser): @@ -197,7 +196,7 @@ def _run(self, log, core_version): elif status == TankCoreUpdater.UPDATE_POSSIBLE: - (summary, url) = installer.get_release_notes() + summary, url = installer.get_release_notes() log.info("") log.info("Newer version %s is available." % new_version) diff --git a/python/tk-core/python/tank/commands/desktop_migration.py b/python/tk-core/python/tank/commands/desktop_migration.py index 90f671ed..92ffc3fb 100644 --- a/python/tk-core/python/tank/commands/desktop_migration.py +++ b/python/tk-core/python/tank/commands/desktop_migration.py @@ -8,9 +8,8 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -from .action_base import Action -from . import console_utils from . import constants +from .action_base import Action _MESSAGE = ( "This command will migrate the PTR site configuration used by the Desktop app so " diff --git a/python/tk-core/python/tank/commands/dump_config.py b/python/tk-core/python/tank/commands/dump_config.py index d0f68b21..12c7f2ab 100644 --- a/python/tk-core/python/tank/commands/dump_config.py +++ b/python/tk-core/python/tank/commands/dump_config.py @@ -12,8 +12,8 @@ from io import StringIO from ..errors import TankError -from .action_base import Action from ..util import filesystem +from .action_base import Action class DumpConfigAction(Action): diff --git a/python/tk-core/python/tank/commands/get_entity_commands.py b/python/tk-core/python/tank/commands/get_entity_commands.py index f22424a4..a18d41dc 100644 --- a/python/tk-core/python/tank/commands/get_entity_commands.py +++ b/python/tk-core/python/tank/commands/get_entity_commands.py @@ -7,16 +7,15 @@ # By accessing, using, copying or modifying this work you indicate your # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. +import itertools +import operator +import os import sys -from .action_base import Action from ..errors import TankError -from ..util.process import SubprocessCalledProcessError, subprocess_check_output from ..util import is_linux, is_macos, is_windows - -import itertools -import operator -import os +from ..util.process import SubprocessCalledProcessError, subprocess_check_output +from .action_base import Action def execute_tank_command(pipeline_config_path, args): @@ -150,7 +149,7 @@ def run_noninteractive(self, log, parameters): per_entity_type = itertools.groupby(entities, operator.itemgetter(0)) commands_per_entity = {} - for (entity_type, entities_of_type) in per_entity_type: + for entity_type, entities_of_type in per_entity_type: # make a list out of the grouped entity tuples entities_of_type = list(entities_of_type) try: @@ -311,7 +310,7 @@ def _parse_cached_commands(self, commands_data): tokens += [""] * (NUM_EXPECTED_TOKENS - len(tokens)) # extract the information from the tokens - (name, title, _, _, icon, description) = tuple(tokens) + name, title, _, _, icon, description = tuple(tokens) commands.append( {"name": name, "title": title, "icon": icon, "description": description} diff --git a/python/tk-core/python/tank/commands/install.py b/python/tk-core/python/tank/commands/install.py index 0d1fd957..baf7dfda 100644 --- a/python/tk-core/python/tank/commands/install.py +++ b/python/tk-core/python/tank/commands/install.py @@ -9,10 +9,8 @@ # not expressly granted therein are reserved by Shotgun Software Inc. from ..errors import TankError -from . import console_utils +from . import console_utils, constants, util from .action_base import Action -from . import util -from . import constants class InstallAppAction(Action): @@ -94,7 +92,7 @@ def run_interactive(self, log, args): :param log: std python logger :param args: command line args """ - (use_legacy_parser, args) = util.should_use_legacy_yaml_parser(args) + use_legacy_parser, args = util.should_use_legacy_yaml_parser(args) preserve_yaml = not use_legacy_parser if len(args) != 3: @@ -411,7 +409,7 @@ def run_interactive(self, log, args): :param log: std python logger :param args: command line args """ - (use_legacy_parser, args) = util.should_use_legacy_yaml_parser(args) + use_legacy_parser, args = util.should_use_legacy_yaml_parser(args) preserve_yaml = not use_legacy_parser if len(args) != 2: @@ -539,8 +537,8 @@ def _run(self, log, env_name, engine_name, preserve_yaml): # run descriptor factory method log.info("Connecting to git...") location = {"type": "git", "path": engine_name} - engine_descriptor = self.tk.pipeline_configuration.get_latest_engine_descriptor( - location + engine_descriptor = ( + self.tk.pipeline_configuration.get_latest_engine_descriptor(location) ) log.info( "Latest tag in repository '%s' is %s." @@ -559,8 +557,8 @@ def _run(self, log, env_name, engine_name, preserve_yaml): # this is an app store app! log.info("Connecting to the Toolkit App Store...") location = {"type": "app_store", "name": engine_name} - engine_descriptor = self.tk.pipeline_configuration.get_latest_engine_descriptor( - location + engine_descriptor = ( + self.tk.pipeline_configuration.get_latest_engine_descriptor(location) ) log.info( "Latest approved App Store Version is %s." % engine_descriptor.version diff --git a/python/tk-core/python/tank/commands/interaction.py b/python/tk-core/python/tank/commands/interaction.py index 0fed12b3..5229db4d 100644 --- a/python/tk-core/python/tank/commands/interaction.py +++ b/python/tk-core/python/tank/commands/interaction.py @@ -14,7 +14,6 @@ from .. import LogManager - log = LogManager.get_logger(__name__) diff --git a/python/tk-core/python/tank/commands/misc.py b/python/tk-core/python/tank/commands/misc.py index f4d1b911..5694b686 100644 --- a/python/tk-core/python/tank/commands/misc.py +++ b/python/tk-core/python/tank/commands/misc.py @@ -8,12 +8,12 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -from ..errors import TankError -from .action_base import Action - import code -import sys import os +import sys + +from ..errors import TankError +from .action_base import Action class ClearCacheAction(Action): @@ -70,7 +70,7 @@ def _run(self, log): log.debug("Deleting cache file %s..." % full_path) try: os.remove(full_path) - except: + except Exception: log.warning("Could not delete cache file '%s'!" % full_path) log.info("The PTR menu cache has been cleared.") @@ -128,8 +128,8 @@ def run_interactive(self, log, args): # attempt install tab command completion try: - import rlcompleter import readline + import rlcompleter # noqa: F401 if "libedit" in readline.__doc__: # macosx, some versions - see @@ -137,7 +137,7 @@ def run_interactive(self, log, args): readline.parse_and_bind("bind ^I rl_complete") else: readline.parse_and_bind("tab: complete") - except: + except Exception: pass code.interact(banner="\n".join(msg), local=tk_locals) diff --git a/python/tk-core/python/tank/commands/move_pc.py b/python/tk-core/python/tank/commands/move_pc.py index 24017746..5aac6c9a 100644 --- a/python/tk-core/python/tank/commands/move_pc.py +++ b/python/tk-core/python/tank/commands/move_pc.py @@ -8,15 +8,14 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -from . import constants -from ..errors import TankError - -from .action_base import Action - import os import shutil import sys +from ..errors import TankError +from . import constants +from .action_base import Action + class MovePCAction(Action): """ diff --git a/python/tk-core/python/tank/commands/path_cache.py b/python/tk-core/python/tank/commands/path_cache.py index bd7554a7..11218f7a 100644 --- a/python/tk-core/python/tank/commands/path_cache.py +++ b/python/tk-core/python/tank/commands/path_cache.py @@ -12,10 +12,8 @@ Methods relating to the path cache """ +from .. import folder, path_cache from ..errors import TankError -from .. import path_cache -from .. import folder - from .action_base import Action @@ -176,7 +174,9 @@ def run_interactive(self, log, args): # first load up the current path cache file and make sure # shotgun has got all those entries present as FilesystemLocations. log.info("") - log.info("Phase 1/3: Pushing data from the current path cache to Flow Production Tracking...") + log.info( + "Phase 1/3: Pushing data from the current path cache to Flow Production Tracking..." + ) curr_pc = path_cache.PathCache(self.tk) try: curr_pc.ensure_all_entries_are_in_shotgun() @@ -191,7 +191,9 @@ def run_interactive(self, log, args): # and synchronize path cache log.info("") - log.info("Phase 3/3: Synchronizing your local machine with Flow Production Tracking...") + log.info( + "Phase 3/3: Synchronizing your local machine with Flow Production Tracking..." + ) pc = path_cache.PathCache(self.tk) try: pc.synchronize(full_sync=True) diff --git a/python/tk-core/python/tank/commands/pc_overview.py b/python/tk-core/python/tank/commands/pc_overview.py index 76b1b48e..d3568441 100644 --- a/python/tk-core/python/tank/commands/pc_overview.py +++ b/python/tk-core/python/tank/commands/pc_overview.py @@ -9,14 +9,14 @@ # not expressly granted therein are reserved by Shotgun Software Inc. +import os + from .. import pipelineconfig_utils +from ..errors import TankError from ..util import ShotgunPath from . import constants -from ..errors import TankError from .action_base import Action -import os - class PCBreakdownAction(Action): """ diff --git a/python/tk-core/python/tank/commands/push_pc.py b/python/tk-core/python/tank/commands/push_pc.py index aa5f6e47..faa27e7d 100644 --- a/python/tk-core/python/tank/commands/push_pc.py +++ b/python/tk-core/python/tank/commands/push_pc.py @@ -8,21 +8,16 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -from ..util import filesystem -from . import constants +import datetime +import os +import shutil + from ..errors import TankError from ..pipelineconfig import PipelineConfiguration - -from . import console_utils - +from ..util import ShotgunPath, filesystem +from . import constants from .action_base import Action -from ..util import ShotgunPath - -import os -import datetime -import shutil - # Core configuration files which are associated with the core API installation and not # the pipeline configuration. CORE_API_FILES = [ @@ -136,7 +131,7 @@ def run_interactive(self, log, args): raise TankError("Aborted by user.") try: target_pc_id = int(answer) - except: + except Exception: raise TankError("Please enter a number!") self._run( diff --git a/python/tk-core/python/tank/commands/setup_project.py b/python/tk-core/python/tank/commands/setup_project.py index 4ed10d33..44b9184f 100644 --- a/python/tk-core/python/tank/commands/setup_project.py +++ b/python/tk-core/python/tank/commands/setup_project.py @@ -13,19 +13,15 @@ import textwrap import traceback -from .action_base import Action -from . import core_localize -from ..errors import TankError -from ..util import shotgun -from ..util import ShotgunPath -from ..util import is_linux, is_macos, is_windows -from . import constants from .. import pipelineconfig_utils +from ..errors import TankError +from ..util import ShotgunPath, is_linux, is_macos, is_windows, shotgun from ..util.filesystem import ensure_folder_exists - +from . import constants, core_localize +from .action_base import Action +from .interaction import YesToEverythingInteraction from .setup_project_core import run_project_setup from .setup_project_params import ProjectSetupParameters -from .interaction import YesToEverythingInteraction class SetupProjectAction(Action): @@ -483,7 +479,7 @@ def _select_project(self, log, sg, show_initialized_projects): ["archived", "is_not", True], ] - if show_initialized_projects == False: + if show_initialized_projects is False: # not force mode. Only show non-set up projects filters.append(["tank_name", "is", None]) @@ -545,7 +541,7 @@ def _select_project(self, log, sg, show_initialized_projects): raise TankError("Aborted by user.") try: project_id = int(answer) - except: + except Exception: raise TankError("Please enter a number!") if project_id not in [x["id"] for x in projs]: @@ -687,9 +683,7 @@ def _get_disk_location(self, log, params): default_config_locations = self._get_default_configuration_location(log, params) - linux_path = self._ask_location( - log, default_config_locations["linux"], "Linux" - ) + linux_path = self._ask_location(log, default_config_locations["linux"], "Linux") windows_path = self._ask_location( log, default_config_locations["win32"], "Windows" ) @@ -726,7 +720,6 @@ def _get_default_configuration_location(self, log, params): # location for the installed config. # Multi-root configurations require a storage named "primary" so we base # our default on that. If only a single storage is available, we just use it. - storage_names = params.get_required_storages() default_storage_name = params.default_storage_name # There is no default storage name for a config that doesn't use roots, like @@ -950,7 +943,7 @@ def _map_storages(self, params, config_uri, log, sg): mapped_roots = [] # loop over required storage roots - for (root_name, root_info) in required_roots.items(): + for root_name, root_info in required_roots.items(): log.info("%s" % (root_name,)) log.info("-" * len(root_name)) @@ -1057,7 +1050,7 @@ def _map_storages(self, params, config_uri, log, sg): # ---- now we've mapped the roots, and they're all valid, we need to # update the root information on the core wizard - for (root_name, storage_name) in mapped_roots: + for root_name, storage_name in mapped_roots: root_info = required_roots[root_name] storage_data = storage_by_name[storage_name.lower()] diff --git a/python/tk-core/python/tank/commands/setup_project_core.py b/python/tk-core/python/tank/commands/setup_project_core.py index 38b54395..6f315c19 100644 --- a/python/tk-core/python/tank/commands/setup_project_core.py +++ b/python/tk-core/python/tank/commands/setup_project_core.py @@ -8,18 +8,17 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -import sys import os import shutil - -from . import constants -from ..errors import TankError -from ..util import StorageRoots -from ..util import filesystem -from ..api import sgtk_from_path +import sys from tank_vendor import yaml +from ..api import sgtk_from_path +from ..errors import TankError +from ..util import StorageRoots, filesystem +from . import constants + @filesystem.with_cleared_umask def run_project_setup(log, sg, setup_params): diff --git a/python/tk-core/python/tank/commands/setup_project_params.py b/python/tk-core/python/tank/commands/setup_project_params.py index bd9bed67..3f7cc2aa 100644 --- a/python/tk-core/python/tank/commands/setup_project_params.py +++ b/python/tk-core/python/tank/commands/setup_project_params.py @@ -9,29 +9,21 @@ # not expressly granted therein are reserved by Shotgun Software Inc. import os +import sys import tempfile import uuid -import sys -from . import constants -from . import util +from tank_vendor import yaml -from ..util import StorageRoots +from .. import hook, pipelineconfig_utils +from ..descriptor import Descriptor, create_descriptor +from ..errors import TankError, TankErrorProjectIsSetup +from ..util import ShotgunPath, StorageRoots, filesystem from ..util import sgre as re from ..util import shotgun -from ..util import filesystem -from ..util import is_windows from ..util.version import is_version_newer from ..util.zip import unzip_file, zip_file - -from .. import hook -from ..errors import TankError, TankErrorProjectIsSetup -from .. import pipelineconfig_utils -from ..descriptor import create_descriptor, Descriptor - -from tank_vendor import yaml - -from ..util import ShotgunPath +from . import constants, util class ProjectSetupParameters(object): @@ -57,7 +49,7 @@ class ProjectSetupParameters(object): - run project setup! """ - (CENTRALIZED_CONFIG, DISTRIBUTED_CONFIG) = range(2) + CENTRALIZED_CONFIG, DISTRIBUTED_CONFIG = range(2) def __init__(self, log, sg): """ @@ -514,8 +506,8 @@ def set_project_id(self, project_id, force=False): # if force is false then tank_name must be empty if ( - self.get_auto_path_mode() == False - and force == False + self.get_auto_path_mode() is False + and force is False and proj["tank_name"] is not None ): raise TankErrorProjectIsSetup() @@ -1019,7 +1011,7 @@ def __init__(self, config_uri, sg, log): # now extract the cfg and validate old_umask = os.umask(0) try: - (self._cfg_folder, self._version, self._config_mode) = self._process_config( + self._cfg_folder, self._version, self._config_mode = self._process_config( config_uri ) finally: @@ -1272,7 +1264,7 @@ def resolve_storages(self): storage_info = {} # do the storage lookup and mapping in PTR - (local_storage_lookup, unmapped_roots) = self._storage_roots.get_local_storages( + local_storage_lookup, unmapped_roots = self._storage_roots.get_local_storages( self._sg ) diff --git a/python/tk-core/python/tank/commands/setup_project_wizard.py b/python/tk-core/python/tank/commands/setup_project_wizard.py index 45f11947..465275d9 100644 --- a/python/tk-core/python/tank/commands/setup_project_wizard.py +++ b/python/tk-core/python/tank/commands/setup_project_wizard.py @@ -651,9 +651,9 @@ def get_core_settings(self): ) return_data["using_runtime"] = False return_data["pipeline_config"] = data - return_data[ - "core_path" - ] = pipelineconfig_utils.resolve_all_os_paths_to_core(core_api_root) + return_data["core_path"] = ( + pipelineconfig_utils.resolve_all_os_paths_to_core(core_api_root) + ) # finally, check the logic for localization: # if this core that we have found and resolved is localized, diff --git a/python/tk-core/python/tank/commands/switch.py b/python/tk-core/python/tank/commands/switch.py index b1cbfd3f..d2027995 100644 --- a/python/tk-core/python/tank/commands/switch.py +++ b/python/tk-core/python/tank/commands/switch.py @@ -8,14 +8,12 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. +import os + from ..errors import TankError -from . import constants -from . import util -from . import console_utils +from . import console_utils, constants, util from .action_base import Action -import os - class SwitchAppAction(Action): """ @@ -116,7 +114,7 @@ def run_interactive(self, log, args): log.info("") return - (use_legacy_parser, args) = util.should_use_legacy_yaml_parser(args) + use_legacy_parser, args = util.should_use_legacy_yaml_parser(args) preserve_yaml = not use_legacy_parser # get parameters @@ -203,13 +201,13 @@ def run_interactive(self, log, args): log.info("") log.info("Current version") log.info("------------------------------------") - for (k, v) in descriptor.get_dict().items(): + for k, v in descriptor.get_dict().items(): log.info(" - %s: %s" % (k.capitalize(), v)) log.info("") log.info("New version") log.info("------------------------------------") - for (k, v) in new_descriptor.get_dict().items(): + for k, v in new_descriptor.get_dict().items(): log.info(" - %s: %s" % (k.capitalize(), v)) log.info("") @@ -229,9 +227,7 @@ def run_interactive(self, log, args): # ensure that all required frameworks have been installed # find the file where our item is being installed - (_, yml_file) = env.find_location_for_app( - engine_instance_name, app_instance_name - ) + _, yml_file = env.find_location_for_app(engine_instance_name, app_instance_name) console_utils.ensure_frameworks_installed( log, self.tk, yml_file, new_descriptor, env, self._interaction_interface diff --git a/python/tk-core/python/tank/commands/tank_command.py b/python/tk-core/python/tank/commands/tank_command.py index 8c5171e1..d803b20c 100644 --- a/python/tk-core/python/tank/commands/tank_command.py +++ b/python/tk-core/python/tank/commands/tank_command.py @@ -15,38 +15,38 @@ import logging -from .action_base import Action -from .interaction import RawInputCommandInteraction, YesToEverythingInteraction -from . import folders -from . import misc -from . import move_pc -from . import pc_overview -from . import path_cache -from . import update -from . import push_pc -from . import setup_project -from . import setup_project_wizard -from . import dump_config -from . import validate_config -from . import cache_apps -from . import switch -from . import app_info -from . import core_upgrade -from . import core_localize -from . import install -from . import clone_configuration -from . import copy_apps -from . import unregister_folders -from . import desktop_migration -from . import cache_yaml -from . import get_entity_commands -from . import constants - - -from .. import constants as constants_global from .. import LogManager -from ..platform.engine import start_engine, get_environment_from_context +from .. import constants as constants_global from ..errors import TankError +from ..platform.engine import get_environment_from_context, start_engine +from . import ( + app_info, + cache_apps, + cache_yaml, + clone_configuration, + constants, + copy_apps, + core_localize, + core_upgrade, + desktop_migration, + dump_config, + folders, + get_entity_commands, + install, + misc, + move_pc, + path_cache, + pc_overview, + push_pc, + setup_project, + setup_project_wizard, + switch, + unregister_folders, + update, + validate_config, +) +from .action_base import Action +from .interaction import RawInputCommandInteraction, YesToEverythingInteraction log = LogManager.get_logger(__name__) @@ -473,7 +473,7 @@ def run_action(log, tk, ctx, command, args): found_action = x break - if found_action and found_action.wants_running_shell_engine == False: + if found_action and found_action.wants_running_shell_engine is False: log.debug("No need to load up the engine for this command.") else: # try to load the engine. diff --git a/python/tk-core/python/tank/commands/unregister_folders.py b/python/tk-core/python/tank/commands/unregister_folders.py index 7aeb58e8..8b61967f 100644 --- a/python/tk-core/python/tank/commands/unregister_folders.py +++ b/python/tk-core/python/tank/commands/unregister_folders.py @@ -12,10 +12,9 @@ Method to unregister folders from the path cache """ -from ..errors import TankError from .. import path_cache +from ..errors import TankError from .action_base import Action -from ..util.login import get_current_user class UnregisterFoldersAction(Action): diff --git a/python/tk-core/python/tank/commands/update.py b/python/tk-core/python/tank/commands/update.py index 2b8129e5..2f3b1e64 100644 --- a/python/tk-core/python/tank/commands/update.py +++ b/python/tk-core/python/tank/commands/update.py @@ -10,14 +10,12 @@ import os -from .action_base import Action -from . import console_utils -from . import util -from ..platform.environment import WritableEnvironment -from ..descriptor import CheckVersionConstraintsError -from . import constants -from ..util.version import is_version_number, is_version_newer from .. import pipelineconfig_utils +from ..descriptor import CheckVersionConstraintsError +from ..platform.environment import WritableEnvironment +from ..util.version import is_version_newer, is_version_number +from . import console_utils, constants, util +from .action_base import Action class AppUpdatesAction(Action): @@ -110,7 +108,7 @@ def run_interactive(self, log, args): :param log: std python logger :param args: command line args """ - (use_legacy_parser, args) = util.should_use_legacy_yaml_parser(args) + use_legacy_parser, args = util.should_use_legacy_yaml_parser(args) preserve_yaml = not use_legacy_parser if len(args) == 0: @@ -368,7 +366,7 @@ def _check_for_updates( x["new_descriptor"].version, ) ) - (_, url) = x["new_descriptor"].changelog + _, url = x["new_descriptor"].changelog if url: summary.append("Change Log: %s" % url) summary.append("") @@ -512,11 +510,11 @@ def _update_item( # ensure that all required frameworks have been installed # find the file where our item is being installed if framework_name: - (_, yml_file) = env.find_location_for_framework(framework_name) + _, yml_file = env.find_location_for_framework(framework_name) elif app_name: - (_, yml_file) = env.find_location_for_app(engine_name, app_name) + _, yml_file = env.find_location_for_app(engine_name, app_name) else: - (_, yml_file) = env.find_location_for_engine(engine_name) + _, yml_file = env.find_location_for_engine(engine_name) console_utils.ensure_frameworks_installed( log, tk, yml_file, new_descriptor, env, self._interaction_interface @@ -791,7 +789,7 @@ def _check_item_update_status( out_of_date = latest_desc.version != curr_desc.version # check deprecation - (is_dep, dep_msg) = latest_desc.deprecation_status + is_dep, dep_msg = latest_desc.deprecation_status if is_dep: # we treat deprecation as an out of date that cannot be upgraded! diff --git a/python/tk-core/python/tank/commands/validate_config.py b/python/tk-core/python/tank/commands/validate_config.py index ce2d6b80..539ca38c 100644 --- a/python/tk-core/python/tank/commands/validate_config.py +++ b/python/tk-core/python/tank/commands/validate_config.py @@ -9,9 +9,10 @@ # not expressly granted therein are reserved by Shotgun Software Inc. import os -from .action_base import Action + from ..errors import TankError -from ..platform import validation, bundle +from ..platform import bundle, validation +from .action_base import Action class ValidateConfigAction(Action): @@ -83,9 +84,7 @@ def _run(self, log, parameters): log.info("") log.info("") - log.info( - "Welcome to the Flow Production Tracking Configuration validator!" - ) + log.info("Welcome to the Flow Production Tracking Configuration validator!") log.info("") log.info("Found the following environments:") diff --git a/python/tk-core/python/tank/context.py b/python/tk-core/python/tank/context.py index 4fe363ed..8c3757c8 100644 --- a/python/tk-core/python/tank/context.py +++ b/python/tk-core/python/tank/context.py @@ -13,22 +13,22 @@ """ -import os +from __future__ import annotations # needed for python 3.9 support + import copy import json +import os from tank_vendor import yaml -from . import authentication - -from .util import login -from .util import shotgun_entity -from .util import shotgun -from .util import get_sg_entity_name_field -from .util import pickle -from . import constants -from .errors import TankError, TankContextDeserializationError +from tank_vendor.flow_integration_sdk import sandbox + +from . import authentication, constants +from .authentication import flow_auth +from .errors import TankContextDeserializationError, TankError +from .flowam import constants as flow_const from .path_cache import PathCache from .template import TemplatePath +from .util import login, pickle, shotgun, shotgun_entity class Context(object): @@ -72,6 +72,7 @@ def __init__( user=None, additional_entities=None, source_entity=None, + flow_draft_id=None, ): """ Context objects are not constructed by hand but are fabricated by the @@ -86,6 +87,7 @@ def __init__( self.__user = user self.__additional_entities = additional_entities or [] self.__source_entity = source_entity + self.__flow_draft_id = flow_draft_id self._entity_fields_cache = {} def __repr__(self): @@ -176,9 +178,9 @@ def _entity_dicts_eq(d1, d2): :param d2: Second entity dictionary :returns: True if d1 and d2 are considered equal, otherwise False. """ - if d1 == d2 == None: + if d1 is None and d2 is None: return True - if d1 == None or d2 == None: + if d1 is None or d2 is None: return False return d1["type"] == d2["type"] and d1["id"] == d2["id"] @@ -259,6 +261,7 @@ def __deepcopy__(self, memo): ctx_copy.__user = copy.deepcopy(self.__user, memo) ctx_copy.__additional_entities = copy.deepcopy(self.__additional_entities, memo) ctx_copy.__source_entity = copy.deepcopy(self.__source_entity, memo) + ctx_copy.__flow_draft_id = copy.deepcopy(self.__flow_draft_id, memo) # except: # ctx_copy._entity_fields_cache @@ -403,6 +406,45 @@ def additional_entities(self): """ return self.__additional_entities + @property + def flow_project_id(self) -> str | None: + """ + The FlowAM project ID for this context, or ``None`` if the project is + not FlowAM-enabled or there is no project in this context. + + The value is read from the ``sg_flow_am_id`` field on the project dict. + It is populated by the bootstrap manager after it queries ShotGrid. + + :returns: A string containing the FlowAM project ID, or ``None``. + :rtype: str or None + """ + if self.project: + return self.project.get(flow_auth.AM_READY_PROJECT_FIELD) + return None + + @property + def flow_draft_id(self) -> str | None: + """Current Flow draft context. + If not None, this designates the current draft being worked on within the DCC session. + """ + return self.__flow_draft_id + + @property + def flow_schema_version(self) -> str | None: + """ + The FlowAM schema version of the project in context, or ``None`` if the project is + not FlowAM-enabled or there is no schema created. + + The value is read from the ``sg_flow_schema_config_version`` field on the project dict. + It is populated by the bootstrap manager after it queries ShotGrid. + + :returns: A string containing the FlowAM schema version, or ``None``. + :rtype: str or None + """ + if self.project: + return self.project.get(flow_const.FLOW_SCHEMA_VERSION_FIELD) + return None + @property def entity_locations(self): """ @@ -708,6 +750,19 @@ def create_copy_for_user(self, user): ctx_copy.__user = user return ctx_copy + def set_flow_context(self, file_path: str): + """Set the current flow asset context based on opened file path. + + Args: + file_path: File currently opened in DCC. + """ + self.__flow_draft_id = sandbox.get_draft_context(file_path) + + def clear_flow_context(self): + """Clear the current flow asset context.""" + + self.__flow_draft_id = None + ################################################################################################ # serialization @@ -815,8 +870,8 @@ def deserialize(cls, context_str): def to_dict(self): """ Converts the context into a dictionary with keys ``project``, - ``entity``, ``user``, ``step``, ``task``, ``additional_entities`` and - ``source_entity``. + ``entity``, ``user``, ``step``, ``task``, ``additional_entities``, + ``source_entity``, ``flow_project_id``, ``flow_draft_id`` and ``flow_schema_version``. .. note :: Contrary to :meth:`Context.serialize`, this method discards information @@ -835,6 +890,9 @@ def to_dict(self): self._cleanup_entity(entity) for entity in self.additional_entities ], "source_entity": self._cleanup_entity(self.source_entity), + "flow_project_id": self.flow_project_id, + "flow_draft_id": self.flow_draft_id, + "flow_schema_version": self.flow_schema_version, } def _cleanup_entity(self, entity): @@ -894,7 +952,7 @@ def _from_dict(cls, data): :returns: :class:`Context` """ - return Context( + ctx = Context( tk=data.get("tk"), project=data.get("project"), entity=data.get("entity"), @@ -903,7 +961,23 @@ def _from_dict(cls, data): user=data.get("user"), additional_entities=data.get("additional_entities"), source_entity=data.get("source_entity"), + flow_draft_id=data.get("flow_draft_id"), ) + if ( + ctx.project is not None + and "flow_project_id" in data + and flow_auth.AM_READY_PROJECT_FIELD not in ctx.project + ): + ctx.project[flow_auth.AM_READY_PROJECT_FIELD] = data["flow_project_id"] + if ( + ctx.project is not None + and "flow_schema_version" in data + and flow_const.FLOW_SCHEMA_VERSION_FIELD not in ctx.project + ): + ctx.project[flow_const.FLOW_SCHEMA_VERSION_FIELD] = data[ + "flow_schema_version" + ] + return ctx ################################################################################################ # private methods @@ -931,7 +1005,7 @@ def _fields_from_shotgun(self, template, entities, validate): # this key is a shotgun value that needs fetching! # ensure that the context actually provides the desired entities - if not key.shotgun_entity_type in entities: + if key.shotgun_entity_type not in entities: if validate: raise TankError( "Key '%s' in template '%s' could not be populated by " @@ -1305,7 +1379,8 @@ def _from_entity_type_and_id(tk, entity, source_entity=None): if sg_entity is None: raise TankError( - "Entity %s with id %s not found in Flow Production Tracking!" % (entity_type, entity_id) + "Entity %s with id %s not found in Flow Production Tracking!" + % (entity_type, entity_id) ) if sg_entity.get("task"): @@ -1412,7 +1487,6 @@ def _from_entity_dictionary(tk, entity_dictionary, source_entity=None): } entity_type = entity_dictionary["type"] - entity_id = entity_dictionary["id"] # try to determine the various entities from the entity dictionary: project = None @@ -1477,7 +1551,7 @@ def _build_clean_entity(ent): if "id" not in ent or "type" not in ent: return None ent_name = _get_entity_name(ent) - if ent_name == None: + if ent_name is None: return None # return a clean dictionary: return {"type": ent["type"], "id": ent["id"], "name": ent_name} @@ -1719,7 +1793,7 @@ def context_yaml_representer(dumper, context): # pipeline config path as part of the dict context_dict["_pc_path"] = context.tank.pipeline_configuration.get_path() - return dumper.represent_mapping(u"!TankContext", context_dict) + return dumper.represent_mapping("!TankContext", context_dict) def context_yaml_constructor(loader, node): @@ -1750,7 +1824,7 @@ def context_yaml_constructor(loader, node): yaml.add_representer(Context, context_yaml_representer) -yaml.add_constructor(u"!TankContext", context_yaml_constructor) +yaml.add_constructor("!TankContext", context_yaml_constructor) ################################################################################################ # utility methods @@ -1767,7 +1841,7 @@ def _get_entity_name(entity_dictionary): """ name_field = shotgun_entity.get_sg_entity_name_field(entity_dictionary["type"]) entity_name = entity_dictionary.get(name_field) - if entity_name == None: + if entity_name is None: # Also check to see if entity contains 'name': if name_field != "name": entity_name = entity_dictionary.get("name") @@ -1807,7 +1881,9 @@ def _task_from_sg(tk, task_id, additional_fields=None): "Task", [["id", "is", task_id]], standard_fields + additional_fields ) if not task: - raise TankError("Unable to locate Task with id %s in Flow Production Tracking" % task_id) + raise TankError( + "Unable to locate Task with id %s in Flow Production Tracking" % task_id + ) # add task so it can be processed with other shotgun entities task["task"] = {"type": "Task", "id": task_id, "name": task["content"]} @@ -1867,7 +1943,8 @@ def _entity_from_sg(tk, entity_type, entity_id): if not data: raise TankError( - "Unable to locate %s with id %s in Flow Production Tracking" % (entity_type, entity_id) + "Unable to locate %s with id %s in Flow Production Tracking" + % (entity_type, entity_id) ) # create context diff --git a/python/tk-core/python/tank/deploy/__init__.py b/python/tk-core/python/tank/deploy/__init__.py index 7f72de65..b129e819 100644 --- a/python/tk-core/python/tank/deploy/__init__.py +++ b/python/tk-core/python/tank/deploy/__init__.py @@ -8,6 +8,4 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -from . import descriptor -from . import dev_descriptor -from . import util +from . import descriptor, dev_descriptor, util diff --git a/python/tk-core/python/tank/deploy/descriptor.py b/python/tk-core/python/tank/deploy/descriptor.py index b8482eba..c6fcd948 100644 --- a/python/tk-core/python/tank/deploy/descriptor.py +++ b/python/tk-core/python/tank/deploy/descriptor.py @@ -14,7 +14,7 @@ This code may be removed at some point in the future. """ -from ..descriptor import create_descriptor, Descriptor +from ..descriptor import Descriptor, create_descriptor from ..util import shotgun diff --git a/python/tk-core/python/tank/deploy/util.py b/python/tk-core/python/tank/deploy/util.py index be85eb0d..9b55d6ba 100644 --- a/python/tk-core/python/tank/deploy/util.py +++ b/python/tk-core/python/tank/deploy/util.py @@ -16,4 +16,4 @@ # import methods to ensure that older version of the desktop engine # will function correctly - the code calls these internal methods -from ..util.version import is_version_newer, is_version_older +from ..util.version import is_version_newer, is_version_older # noqa: F401 diff --git a/python/tk-core/python/tank/descriptor/__init__.py b/python/tk-core/python/tank/descriptor/__init__.py index 2031cc02..b8b82e15 100644 --- a/python/tk-core/python/tank/descriptor/__init__.py +++ b/python/tk-core/python/tank/descriptor/__init__.py @@ -10,22 +10,20 @@ from .descriptor import Descriptor, create_descriptor -from .descriptor_core import CoreDescriptor -from .descriptor_bundle import AppDescriptor, FrameworkDescriptor, EngineDescriptor +from .descriptor_bundle import AppDescriptor, EngineDescriptor, FrameworkDescriptor from .descriptor_config import ConfigDescriptor - +from .descriptor_core import CoreDescriptor from .errors import ( + CheckVersionConstraintsError, + InvalidAppStoreCredentialsError, TankAppStoreConnectionError, TankAppStoreError, + TankCheckVersionConstraintsError, TankDescriptorError, - InvalidAppStoreCredentialsError, TankInvalidAppStoreCredentialsError, - CheckVersionConstraintsError, - TankCheckVersionConstraintsError, TankInvalidInterpreterLocationError, TankMissingManifestError, ) - from .io_descriptor import ( descriptor_dict_to_uri, descriptor_uri_to_dict, diff --git a/python/tk-core/python/tank/descriptor/descriptor.py b/python/tk-core/python/tank/descriptor/descriptor.py index 0ef5f448..b767fa93 100644 --- a/python/tk-core/python/tank/descriptor/descriptor.py +++ b/python/tk-core/python/tank/descriptor/descriptor.py @@ -8,17 +8,15 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -import os import copy +import os +from .. import constants as constants2 from ..log import LogManager -from ..util import filesystem -from .io_descriptor import create_io_descriptor -from .errors import TankDescriptorError -from ..util import LocalFileStorageManager +from ..util import LocalFileStorageManager, filesystem from . import constants -from .. import constants as constants2 - +from .errors import TankDescriptorError +from .io_descriptor import create_io_descriptor logger = LogManager.get_logger(__name__) diff --git a/python/tk-core/python/tank/descriptor/descriptor_bundle.py b/python/tk-core/python/tank/descriptor/descriptor_bundle.py index 92eb2a9c..0a591262 100644 --- a/python/tk-core/python/tank/descriptor/descriptor_bundle.py +++ b/python/tk-core/python/tank/descriptor/descriptor_bundle.py @@ -8,13 +8,11 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -from .descriptor import Descriptor -from .errors import TankDescriptorError, CheckVersionConstraintsError -from . import constants -from .. import LogManager +from .. import LogManager, pipelineconfig_utils from ..util.version import is_version_older -from ..util import shotgun -from .. import pipelineconfig_utils +from . import constants +from .descriptor import Descriptor +from .errors import CheckVersionConstraintsError, TankDescriptorError log = LogManager.get_logger(__name__) diff --git a/python/tk-core/python/tank/descriptor/descriptor_cached_config.py b/python/tk-core/python/tank/descriptor/descriptor_cached_config.py index 15ddb494..5c826420 100644 --- a/python/tk-core/python/tank/descriptor/descriptor_cached_config.py +++ b/python/tk-core/python/tank/descriptor/descriptor_cached_config.py @@ -12,10 +12,10 @@ from tank_vendor import yaml +from .. import LogManager from . import constants -from .errors import TankDescriptorError from .descriptor_config import ConfigDescriptor -from .. import LogManager +from .errors import TankDescriptorError log = LogManager.get_logger(__name__) @@ -69,7 +69,7 @@ def associated_core_descriptor(self): # # location: # name: tk-core - # type: app_store + # type: app_store # version: v0.16.34 log.debug("Detected core descriptor file '%s'" % core_descriptor_path) diff --git a/python/tk-core/python/tank/descriptor/descriptor_config.py b/python/tk-core/python/tank/descriptor/descriptor_config.py index ca8e00da..e86f12ba 100644 --- a/python/tk-core/python/tank/descriptor/descriptor_config.py +++ b/python/tk-core/python/tank/descriptor/descriptor_config.py @@ -10,16 +10,14 @@ import os +from .. import LogManager from ..errors import TankFileDoesNotExistError +from ..util import ShotgunPath, StorageRoots +from ..util.version import is_version_older from . import constants -from .errors import TankInvalidInterpreterLocationError from .descriptor import Descriptor, create_descriptor -from .io_descriptor import is_descriptor_version_missing -from .. import LogManager -from ..util import StorageRoots -from ..util import ShotgunPath -from ..util.version import is_version_older -from .io_descriptor import descriptor_uri_to_dict +from .errors import TankInvalidInterpreterLocationError +from .io_descriptor import descriptor_uri_to_dict, is_descriptor_version_missing log = LogManager.get_logger(__name__) diff --git a/python/tk-core/python/tank/descriptor/descriptor_core.py b/python/tk-core/python/tank/descriptor/descriptor_core.py index 904899d9..3a639f6c 100644 --- a/python/tk-core/python/tank/descriptor/descriptor_core.py +++ b/python/tk-core/python/tank/descriptor/descriptor_core.py @@ -8,9 +8,9 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. +from . import constants from .descriptor import Descriptor from .errors import TankMissingManifestError -from . import constants class CoreDescriptor(Descriptor): diff --git a/python/tk-core/python/tank/descriptor/descriptor_installed_config.py b/python/tk-core/python/tank/descriptor/descriptor_installed_config.py index 1c2e3927..7c33a318 100644 --- a/python/tk-core/python/tank/descriptor/descriptor_installed_config.py +++ b/python/tk-core/python/tank/descriptor/descriptor_installed_config.py @@ -10,18 +10,16 @@ import os -from .descriptor_config import ConfigDescriptor -from .. import pipelineconfig_utils -from .. import LogManager -from ..util import ShotgunPath -from . import constants -from .errors import TankMissingManifestError - +from .. import LogManager, pipelineconfig_utils from ..errors import ( - TankNotPipelineConfigurationError, TankFileDoesNotExistError, TankInvalidCoreLocationError, + TankNotPipelineConfigurationError, ) +from ..util import ShotgunPath +from . import constants +from .descriptor_config import ConfigDescriptor +from .errors import TankMissingManifestError log = LogManager.get_logger(__name__) diff --git a/python/tk-core/python/tank/descriptor/errors.py b/python/tk-core/python/tank/descriptor/errors.py index 3ea15ae0..5774355d 100644 --- a/python/tk-core/python/tank/descriptor/errors.py +++ b/python/tk-core/python/tank/descriptor/errors.py @@ -12,8 +12,8 @@ All custom exceptions that this module emits are defined here. """ -from ..errors import TankError from .. import errors +from ..errors import TankError class TankDescriptorError(TankError): diff --git a/python/tk-core/python/tank/descriptor/io_descriptor/__init__.py b/python/tk-core/python/tank/descriptor/io_descriptor/__init__.py index 50763697..e294b962 100644 --- a/python/tk-core/python/tank/descriptor/io_descriptor/__init__.py +++ b/python/tk-core/python/tank/descriptor/io_descriptor/__init__.py @@ -10,8 +10,8 @@ from .factory import ( create_io_descriptor, - descriptor_uri_to_dict, descriptor_dict_to_uri, + descriptor_uri_to_dict, is_descriptor_version_missing, ) @@ -22,15 +22,15 @@ def _initialize_descriptor_factory(): This complex process for handling the IODescriptor abstract factory management is in order to avoid local imports in classes. """ - from .base import IODescriptorBase from .appstore import IODescriptorAppStore + from .base import IODescriptorBase from .dev import IODescriptorDev - from .path import IODescriptorPath - from .shotgun_entity import IODescriptorShotgunEntity - from .git_tag import IODescriptorGitTag from .git_branch import IODescriptorGitBranch + from .git_tag import IODescriptorGitTag from .github_release import IODescriptorGithubRelease from .manual import IODescriptorManual + from .path import IODescriptorPath + from .shotgun_entity import IODescriptorShotgunEntity IODescriptorBase.register_descriptor_factory("app_store", IODescriptorAppStore) IODescriptorBase.register_descriptor_factory("dev", IODescriptorDev) diff --git a/python/tk-core/python/tank/descriptor/io_descriptor/appstore.py b/python/tk-core/python/tank/descriptor/io_descriptor/appstore.py index 746b6c54..d6bfdf5a 100644 --- a/python/tk-core/python/tank/descriptor/io_descriptor/appstore.py +++ b/python/tk-core/python/tank/descriptor/io_descriptor/appstore.py @@ -65,7 +65,7 @@ class IODescriptorAppStore(IODescriptorDownloadable): _app_store_connections = {} # internal app store mappings - (APP, FRAMEWORK, ENGINE, CONFIG, CORE) = range(5) + APP, FRAMEWORK, ENGINE, CONFIG, CORE = range(5) _APP_STORE_OBJECT = { constants.DESCRIPTOR_APP: constants.TANK_APP_ENTITY_TYPE, @@ -231,7 +231,7 @@ def __refresh_metadata(self, path, sg_bundle_data=None, sg_version_data=None): link_field = self._APP_STORE_LINK[self._bundle_type] # connect to the app store - (sg, _) = self.__create_sg_app_store_connection() + sg, _ = self.__create_sg_app_store_connection() if self._bundle_type == self.CORE: # special handling of core since it doesn't have a high-level 'bundle' entity @@ -413,7 +413,7 @@ def _download_local(self, destination_path): is to be downloaded to. """ # connect to the app store - (sg, script_user) = self.__create_sg_app_store_connection() + sg, script_user = self.__create_sg_app_store_connection() # fetch metadata from sg... metadata = self.__refresh_metadata(destination_path) @@ -445,7 +445,7 @@ def _post_download(self, download_path): # write a stats record to the tank app store try: # connect to the app store - (sg, script_user) = self.__create_sg_app_store_connection() + sg, script_user = self.__create_sg_app_store_connection() # fetch metadata from sg... metadata = self.__refresh_metadata(download_path) @@ -745,7 +745,7 @@ def get_latest_version(self, constraint_pattern=None): ) # connect to the app store - (sg, _) = self.__create_sg_app_store_connection() + sg, _ = self.__create_sg_app_store_connection() # get latest get the filter logic for what to exclude if constants.APP_STORE_QA_MODE_ENV_VAR in os.environ: @@ -981,7 +981,7 @@ def __create_sg_app_store_connection(self): # Connect to associated Shotgun site and retrieve the credentials to use to # connect to the app store site try: - (script_name, script_key) = self.__get_app_store_key_from_shotgun() + script_name, script_key = self.__get_app_store_key_from_shotgun() except urllib.error.HTTPError as e: if e.code == 403: # edge case alert! @@ -994,7 +994,7 @@ def __create_sg_app_store_connection(self): # trigger a refresh of our session token by issuing a shotgun API call self._sg_connection.find_one("HumanUser", []) # and retry - (script_name, script_key) = self.__get_app_store_key_from_shotgun() + script_name, script_key = self.__get_app_store_key_from_shotgun() else: raise @@ -1146,7 +1146,7 @@ def has_remote_access(self): % self ) # connect to the app store - (sg, _) = self.__create_sg_app_store_connection() + sg, _ = self.__create_sg_app_store_connection() log.debug("...connection established: %s" % sg) except Exception as e: log.debug("...could not establish connection: %s" % e) diff --git a/python/tk-core/python/tank/descriptor/io_descriptor/dev.py b/python/tk-core/python/tank/descriptor/io_descriptor/dev.py index 73dfbd76..f5e621c6 100644 --- a/python/tk-core/python/tank/descriptor/io_descriptor/dev.py +++ b/python/tk-core/python/tank/descriptor/io_descriptor/dev.py @@ -8,9 +8,8 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -from .path import IODescriptorPath - from ... import LogManager +from .path import IODescriptorPath log = LogManager.get_logger(__name__) diff --git a/python/tk-core/python/tank/descriptor/io_descriptor/downloadable.py b/python/tk-core/python/tank/descriptor/io_descriptor/downloadable.py index 4d9e4f5f..a875f30d 100644 --- a/python/tk-core/python/tank/descriptor/io_descriptor/downloadable.py +++ b/python/tk-core/python/tank/descriptor/io_descriptor/downloadable.py @@ -12,11 +12,10 @@ import os import uuid -from .base import IODescriptorBase -from ..errors import TankDescriptorIOError -from ...util import filesystem - from ... import LogManager +from ...util import filesystem +from ..errors import TankDescriptorIOError +from .base import IODescriptorBase log = LogManager.get_logger(__name__) diff --git a/python/tk-core/python/tank/descriptor/io_descriptor/factory.py b/python/tk-core/python/tank/descriptor/io_descriptor/factory.py index 4759e031..9cb740e5 100644 --- a/python/tk-core/python/tank/descriptor/io_descriptor/factory.py +++ b/python/tk-core/python/tank/descriptor/io_descriptor/factory.py @@ -10,11 +10,10 @@ import copy +from ... import LogManager from ..errors import TankDescriptorError from .base import IODescriptorBase -from ... import LogManager - log = LogManager.get_logger(__name__) diff --git a/python/tk-core/python/tank/descriptor/io_descriptor/git.py b/python/tk-core/python/tank/descriptor/io_descriptor/git.py index dcaede1e..536aa61d 100644 --- a/python/tk-core/python/tank/descriptor/io_descriptor/git.py +++ b/python/tk-core/python/tank/descriptor/io_descriptor/git.py @@ -8,18 +8,16 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. import os -import uuid import shutil -import tempfile import subprocess +import tempfile +import uuid -from .downloadable import IODescriptorDownloadable from ... import LogManager -from ...util.process import subprocess_check_output, SubprocessCalledProcessError - +from ...util import filesystem, is_windows +from ...util.process import SubprocessCalledProcessError, subprocess_check_output from ..errors import TankError -from ...util import filesystem -from ...util import is_windows +from .downloadable import IODescriptorDownloadable log = LogManager.get_logger(__name__) @@ -115,7 +113,7 @@ def _clone_then_execute_git_commands( log.debug("Checking that git exists and can be executed...") try: output = _check_output(["git", "--version"]) - except: + except Exception: log.exception("Unexpected error:") raise TankGitError( "Cannot execute the 'git' command. Please make sure that git is " @@ -236,7 +234,7 @@ def get_system_name(self): and for folders on disk, e.g. 'tk-maya' """ bn = os.path.basename(self._path) - (name, ext) = os.path.splitext(bn) + name, ext = os.path.splitext(bn) return name def has_remote_access(self): diff --git a/python/tk-core/python/tank/descriptor/io_descriptor/git_branch.py b/python/tk-core/python/tank/descriptor/io_descriptor/git_branch.py index 62951553..bfb779ed 100644 --- a/python/tk-core/python/tank/descriptor/io_descriptor/git_branch.py +++ b/python/tk-core/python/tank/descriptor/io_descriptor/git_branch.py @@ -115,7 +115,7 @@ def _is_latest_commit(self, version, branch): log.debug("Checking if the version is pointing to the latest commit...") try: output = _check_output(["git", "ls-remote", self._path, branch]) - except: + except Exception: log.exception("Unexpected error:") raise TankGitError( "Cannot execute the 'git' command. Please make sure that git is " diff --git a/python/tk-core/python/tank/descriptor/io_descriptor/git_tag.py b/python/tk-core/python/tank/descriptor/io_descriptor/git_tag.py index aa1026ed..33a7d71b 100644 --- a/python/tk-core/python/tank/descriptor/io_descriptor/git_tag.py +++ b/python/tk-core/python/tank/descriptor/io_descriptor/git_tag.py @@ -7,13 +7,13 @@ # By accessing, using, copying or modifying this work you indicate your # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -import os import copy +import os import re -from .git import IODescriptorGit -from ..errors import TankDescriptorError from ... import LogManager +from ..errors import TankDescriptorError +from .git import IODescriptorGit log = LogManager.get_logger(__name__) @@ -51,9 +51,7 @@ def __init__(self, descriptor_dict, sg_connection, bundle_type): ) # call base class - super().__init__( - descriptor_dict, sg_connection, bundle_type - ) + super().__init__(descriptor_dict, sg_connection, bundle_type) # path is handled by base class - all git descriptors # have a path to a repo diff --git a/python/tk-core/python/tank/descriptor/io_descriptor/manual.py b/python/tk-core/python/tank/descriptor/io_descriptor/manual.py index ff11158b..0b86fcff 100644 --- a/python/tk-core/python/tank/descriptor/io_descriptor/manual.py +++ b/python/tk-core/python/tank/descriptor/io_descriptor/manual.py @@ -8,9 +8,10 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. import os -from .base import IODescriptorBase + from ... import LogManager from ..errors import TankDescriptorError +from .base import IODescriptorBase log = LogManager.get_logger(__name__) diff --git a/python/tk-core/python/tank/descriptor/io_descriptor/path.py b/python/tk-core/python/tank/descriptor/io_descriptor/path.py index 2dc603eb..502c44b3 100644 --- a/python/tk-core/python/tank/descriptor/io_descriptor/path.py +++ b/python/tk-core/python/tank/descriptor/io_descriptor/path.py @@ -10,10 +10,10 @@ import os -from .base import IODescriptorBase -from ..errors import TankDescriptorError -from ...util import ShotgunPath from ... import LogManager +from ...util import ShotgunPath +from ..errors import TankDescriptorError +from .base import IODescriptorBase log = LogManager.get_logger(__name__) diff --git a/python/tk-core/python/tank/descriptor/io_descriptor/shotgun_entity.py b/python/tk-core/python/tank/descriptor/io_descriptor/shotgun_entity.py index d04bd57a..62fae486 100644 --- a/python/tk-core/python/tank/descriptor/io_descriptor/shotgun_entity.py +++ b/python/tk-core/python/tank/descriptor/io_descriptor/shotgun_entity.py @@ -64,7 +64,7 @@ class IODescriptorShotgunEntity(IODescriptorDownloadable): The latest version is defined as the current record available in Shotgun. """ - (_MODE_ID_BASED, _MODE_NAME_BASED) = range(2) + _MODE_ID_BASED, _MODE_NAME_BASED = range(2) def __init__(self, descriptor_dict, sg_connection, bundle_type): """ diff --git a/python/tk-core/python/tank/flowam/__init__.py b/python/tk-core/python/tank/flowam/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/python/tk-core/python/tank/flowam/config.json b/python/tk-core/python/tank/flowam/config.json new file mode 100644 index 00000000..16d3ff4b --- /dev/null +++ b/python/tk-core/python/tank/flowam/config.json @@ -0,0 +1,188 @@ +{ + "name": "demo_pipeline", + "version": "1.0.2", + "description": "Flow Toolkit pipeline schema definitions", + "schemas": [ + { + "name": "type.container", + "version": "1.0.0", + "display_name": "Container", + "kind": "type", + "description": "Basic container representing a work unit.", + "inherits": [], + "properties": [] + }, + { + "name": "type.container.asset", + "version": "1.0.0", + "display_name": "Asset Build", + "kind": "type", + "description": "Asset type container.", + "inherits": [ + "$ref:type.container" + ], + "properties": [] + }, + { + "name": "type.container.shot", + "version": "1.0.0", + "display_name": "Shot", + "kind": "type", + "description": "Shot type container.", + "inherits": [ + "$ref:type.container" + ], + "properties": [] + }, + { + "name": "type.pipelineStep", + "version": "1.0.0", + "display_name": "Pipeline Step", + "kind": "type", + "description": "Base type for a step in a production pipeline.", + "inherits": [], + "properties": [] + }, + { + "name": "type.workfile", + "version": "1.0.0", + "display_name": "Workfile", + "kind": "type", + "description": "File-based work unit.", + "inherits": [], + "properties": [] + }, + { + "name": "type.workfile.generic", + "version": "1.0.0", + "display_name": "Generic Workfile", + "kind": "type", + "description": "Generic workfile which can contain arbitrary file types.", + "inherits": [ + "$ref:type.workfile" + ], + "properties": [] + }, + { + "name": "type.workfile.maya", + "version": "1.0.0", + "display_name": "Maya Workfile", + "kind": "type", + "description": "Maya native workfile.", + "inherits": [ + "$ref:type.workfile" + ], + "properties": [] + }, + { + "name": "type.workfile.hou", + "version": "1.0.0", + "display_name": "Houdini Workfile", + "kind": "type", + "description": "Houdini native workfile.", + "inherits": [ + "$ref:type.workfile" + ], + "properties": [] + }, + { + "name": "type.workfile.nuke", + "version": "1.0.0", + "display_name": "Nuke Workfile", + "kind": "type", + "description": "Nuke script workfile.", + "inherits": [ + "$ref:type.workfile" + ], + "properties": [] + }, + { + "name": "type.workfile.usd", + "version": "1.0.0", + "display_name": "USD Workfile", + "kind": "type", + "description": "USD native workfile.", + "inherits": [ + "$ref:type.workfile" + ], + "properties": [] + }, + { + "name": "type.derivative", + "version": "1.0.0", + "display_name": "Derivative", + "kind": "type", + "description": "Derivative output from a workfile, also referred to as a work product.", + "inherits": [], + "properties": [] + }, + { + "name": "type.derivative.maya", + "version": "1.0.0", + "display_name": "Maya Derivative", + "kind": "type", + "description": "Maya native type derivative work product.", + "inherits": [ + "$ref:type.derivative" + ], + "properties": [] + }, + { + "name": "type.derivative.usd", + "version": "1.0.0", + "display_name": "USD Derivative", + "kind": "type", + "description": "Universal Scene Description (USD) derivative work product.", + "inherits": [ + "$ref:type.derivative" + ], + "properties": [] + }, + { + "name": "type.template", + "version": "1.0.0", + "display_name": "Template", + "kind": "type", + "description": "Indicates an asset that is intended to be used as a template for building other assets.", + "inherits": [], + "properties": [] + }, + { + "name": "type.derivative.abc", + "version": "1.0.0", + "display_name": "Alembic Derivative", + "kind": "type", + "description": "Alembic derivative work product.", + "inherits": [ + "$ref:type.derivative" + ], + "properties": [] + }, + { + "name": "type.fileSequence", + "version": "1.0.0", + "display_name": "File Sequence", + "kind": "type", + "description": "Asset contains a file sequence.", + "inherits": [], + "properties": [ + { + "name": "frameStart", + "data_type": "Int32" + }, + { + "name": "frameEnd", + "data_type": "Int32" + }, + { + "name": "frameSet", + "data_type": "String" + }, + { + "name": "fileFormat", + "data_type": "String" + } + ] + } + ] +} diff --git a/python/tk-core/python/tank/flowam/constants.py b/python/tk-core/python/tank/flowam/constants.py new file mode 100644 index 00000000..08352768 --- /dev/null +++ b/python/tk-core/python/tank/flowam/constants.py @@ -0,0 +1,34 @@ +# Copyright (c) 2026 Shotgun Software Inc. +# +# CONFIDENTIAL AND PROPRIETARY +# +# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit +# Source Code License included in this distribution package. See LICENSE. +# By accessing, using, copying or modifying this work you indicate your +# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights +# not expressly granted therein are reserved by Shotgun Software Inc. + +"""Constants for Flow Integration.""" + +import pathlib + +# Flow Settings +# ------------- +# These are settings names that can be configured within a flow.yml file. +# These attributes are used to prepare a Flow Integration SDK session. + +FLOW_AUTH_APP_ID = "auth_application_id" +FLOW_AUTH_BASE_URL = "auth_base_url" +FLOW_AUTH_CALLBACK_URL = "auth_callback_url" +FLOW_ENDPOINT = "endpoint" +FLOW_WEB_URL = "web_url" +FLOW_SANDBOX_ROOT = "sandbox_root" +FLOW_STORAGE_ROOT = "storage_root" + +# Location of config for specifying custom schemas used +# in the toolkit Flow integration +FLOW_SCHEMA_CONFIG_PATH = str(pathlib.Path(__file__).resolve().parent) + "/config.json" + +# SG Project field that stores the current schema config version, +# used to skip schema provisioning when the version already matches. +FLOW_SCHEMA_VERSION_FIELD = "sg_flow_schema_config_version" diff --git a/python/tk-core/python/tank/flowam/create.py b/python/tk-core/python/tank/flowam/create.py new file mode 100644 index 00000000..125989e8 --- /dev/null +++ b/python/tk-core/python/tank/flowam/create.py @@ -0,0 +1,250 @@ +# Copyright (c) 2026 Shotgun Software Inc. +# +# CONFIDENTIAL AND PROPRIETARY +# +# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit +# Source Code License included in this distribution package. See LICENSE. +# By accessing, using, copying or modifying this work you indicate your +# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights +# not expressly granted therein are reserved by Shotgun Software Inc. + +from __future__ import annotations # needed for python 3.9 support + +import re +from enum import Enum + +from tank_vendor.flow_integration_sdk.exceptions import CreateAssetError, FlowError +from tank_vendor.flow_integration_sdk.globals import FOLDER_TYPE_ID +from tank_vendor.flow_integration_sdk.objects import FlowAsset, FlowProject +from tank_vendor.flow_integration_sdk.publish import ( + TypeComponentSpec, + publish_new_asset, +) +from tank_vendor.flow_integration_sdk.schema import get_schema_id +from tank_vendor.flow_integration_sdk.utils import get_logger, trace + +from .utils import BaseInputs + +# Folder names +ASSET_FOLDER = "Assets" +SHOT_FOLDER = "Shots" +GENERIC_FOLDER = "Generic" +TEMPLATE_FOLDER = "Templates" + +# SG entity types +SHOT_TYPE = "Shot" +ASSET_TYPE = "Asset" + +# Custom schema types +ASSET_CONTAINER_TYPE = "type.container.asset" +CONTAINER_TYPE = "type.container" +DERIVATIVE_TYPE = "type.derivative" +GENERIC_WORKFILE_TYPE = "type.workfile.generic" +PIPELINE_STEP_TYPE = "type.pipelineStep" +SHOT_CONTAINER_TYPE = "type.container.shot" +TEMPLATE_TYPE = "type.template" +WORKFILE_TYPE = "type.workfile" + + +class CreateMode(Enum): + """Enum of modes for creating a new asset.""" + + NEW = "new" #: Create a DCC asset from a new scene as the source. + CURRENT = "current" #: Create a DCC asset from the current scene as the source. + TEMPLATE = "template" #: Create a DCC asset from template scene as the source. + GENERIC = "generic" #: Create a generic asset from a specified source file. + + +@trace +def create_asset_hierarchy(inputs: BaseInputs) -> FlowAsset: + """Ensure the folder hierarchy above a new generic workfile exists. + + Returns the immediate parent under which the workfile asset should be + created. + + Args: + inputs: A ``BaseInputs`` instance (or any inputs object with + ``am_project_id``, ``sg_entity_name``, and a ``create_mode`` + compatible with ``CreateMode.GENERIC``). + + Returns: + The parent :class:`FlowAsset` for the new workfile. + """ + root_folder = get_or_create_root_folder(inputs) + + if inputs.sg_entity_name: + parent = get_or_create_workfile_parent(root_folder, inputs) + else: + parent = root_folder + + return parent + + +@trace +def get_or_create_root_folder(inputs: BaseInputs) -> FlowAsset: + """Retrieve (or create) the top-level folder for the new asset. + + Returns: + Folder :class:`FlowAsset`. + + Raises: + CreateAssetError + """ + logger = get_logger(__name__) + + am_project_id = inputs.am_project_id + sg_entity_type = inputs.sg_entity_type + + try: + project = FlowProject(am_project_id) + except FlowError as exc: + msg = f"Invalid Flow project id provided: {am_project_id}" + raise CreateAssetError(data=inputs.asdict(), details=msg) from exc + + if sg_entity_type == SHOT_TYPE: + folder = project.find_child(SHOT_TYPE) + if not folder: + logger.info(f'Creating "{SHOT_TYPE}" folder...') + raw_asset = publish_new_asset( + name=ensure_unique_name(SHOT_TYPE, project), + parent_id=project.id, + description="Folder for Shot assets.", + components=[ + TypeComponentSpec(type_id=FOLDER_TYPE_ID, name=f"Type {SHOT_TYPE}") + ], + ) + folder = FlowAsset(raw_asset) + elif sg_entity_type == ASSET_TYPE: + folder = project.find_child(ASSET_FOLDER) + if not folder: + logger.info(f'Creating "{ASSET_FOLDER}" folder...') + raw_asset = publish_new_asset( + name=ensure_unique_name(ASSET_FOLDER, project), + parent_id=project.id, + description="Folder for Asset Build assets.", + components=[ + TypeComponentSpec(type_id=FOLDER_TYPE_ID, name=f"Type {ASSET_TYPE}") + ], + ) + folder = FlowAsset(raw_asset) + elif inputs.create_mode == CreateMode.GENERIC: + folder = project.find_child(GENERIC_FOLDER) + if not folder: + logger.info(f'Creating "{GENERIC_FOLDER}" folder...') + raw_asset = publish_new_asset( + name=ensure_unique_name(GENERIC_FOLDER, project), + parent_id=project.id, + description="Folder for Generic assets.", + components=[ + TypeComponentSpec( + type_id=FOLDER_TYPE_ID, name=f"Type {GENERIC_FOLDER}" + ) + ], + ) + folder = FlowAsset(raw_asset) + else: + msg = f"Invalid entity type provided: {sg_entity_type}." + raise CreateAssetError(data=inputs.asdict(), details=msg) + + return folder + + +@trace +def get_or_create_workfile_parent( + root_folder: FlowAsset, inputs: BaseInputs +) -> FlowAsset: + """Determine (and create if necessary) the task-level folder that will be + the direct parent of the workfile asset. + + Returns: + The task-folder :class:`FlowAsset`. + """ + logger = get_logger(__name__) + + sg_entity_type = inputs.sg_entity_type + sg_entity_name = inputs.sg_entity_name + sg_pipeline_step = inputs.sg_pipeline_step + sg_task_name = inputs.sg_task_name + + container = root_folder.find_child(sg_entity_name) + if not container: + logger.info( + f'Creating container asset for "{sg_entity_name}" under ' + f'folder "{root_folder.name}"...' + ) + container_type = ( + ASSET_CONTAINER_TYPE + if sg_entity_type == ASSET_TYPE + else SHOT_CONTAINER_TYPE + ) + raw_asset = publish_new_asset( + name=ensure_unique_name(sg_entity_name, root_folder), + parent_id=root_folder.id, + components=[ + TypeComponentSpec( + type_id=get_schema_id(container_type), name=f"Type {container_type}" + ) + ], + ) + container = FlowAsset(raw_asset) + + pipeline_step = container.find_child(sg_pipeline_step) + if not pipeline_step: + logger.info(f'Creating pipeline step asset for "{sg_pipeline_step}"...') + raw_asset = publish_new_asset( + name=ensure_unique_name(sg_pipeline_step, container), + parent_id=container.id, + components=[ + TypeComponentSpec( + type_id=get_schema_id(PIPELINE_STEP_TYPE), + name=f"Type {PIPELINE_STEP_TYPE}", + ) + ], + ) + pipeline_step = FlowAsset(raw_asset) + + task_folder = pipeline_step.find_child(sg_task_name) + if not task_folder: + logger.info(f'Creating task folder asset for "{sg_task_name}"...') + raw_asset = publish_new_asset( + name=ensure_unique_name(sg_task_name, pipeline_step), + parent_id=pipeline_step.id, + description=f'Folder for task "{sg_task_name}".', + components=[ + TypeComponentSpec(type_id=FOLDER_TYPE_ID, name=f"Type {FOLDER_TYPE_ID}") + ], + ) + task_folder = FlowAsset(raw_asset) + + return task_folder + + +def ensure_unique_name(name: str, parent: FlowAsset | FlowProject) -> str: + """Return a unique sibling name under *parent*, adding a numeric suffix + if a child with the same name already exists. + + Example: siblings ``["asset 1", "asset 2 (1)"]``, input ``"asset 2"`` + → returns ``"asset 2 (2)"``. + """ + logger = get_logger(__name__) + + if parent.find_child(name): + copies = parent.find_children(f"{name} (*)") + highest_index = 0 + for c in copies: + m = re.match(rf"{re.escape(name)} \((?P\d+)\)", c.name) + if m: + try: + index = int(m.group("index")) + except ValueError: + continue + if index > highest_index: + highest_index = index + new_name = f"{name} ({highest_index + 1})" + logger.warning( + f'A child named "{name}" already exists under "{parent.name}". ' + f'Renaming to "{new_name}".' + ) + name = new_name + + return name diff --git a/python/tk-core/python/tank/flowam/host.py b/python/tk-core/python/tank/flowam/host.py new file mode 100644 index 00000000..ac09eced --- /dev/null +++ b/python/tk-core/python/tank/flowam/host.py @@ -0,0 +1,199 @@ +# - +# ***************************************************************************** +# Copyright 2026 Autodesk, Inc. All rights reserved. +# +# These coded instructions, statements, and computer programs contain +# unpublished proprietary information written by Autodesk, Inc. and are +# protected by Federal copyright law. They may not be disclosed to third +# parties or copied or duplicated in any form, in whole or in part, without +# the prior written consent of Autodesk, Inc. +# ***************************************************************************** +# + +from __future__ import annotations # needed for python 3.9 support + +from abc import ABC, abstractmethod + +from tank_vendor.flow_integration_sdk.dependency import DependencyData + + +class FlowHost(ABC): + """Abstract base class defining a required interface for DCC/engine + integration with Flow asset management in toolkit. + """ + + #: The schema name associated with workfiles created for this host + WORKFILE_TYPE = "" + #: File extensions associated with host DCC (e.g. "ma" for Maya) + #: Must have at least one value in a subclass implementation + FILE_TYPES: list[str] = [] + #: Host specific mime type mappings + #: key = file extension from FILE_TYPES, value = mime type + MIME_TYPES: dict[str, str] = {} + + def __init__(self, context): + """Base class initialization. + + Args: + context: Sgtk context object to be passed in from engine. + """ + # Store sgtk context + self.context = context + + @abstractmethod + def current_file(self) -> str: + """Return current open file path in dcc.""" + raise NotImplementedError() + + @abstractmethod + def new_scene(self, force: bool = True) -> bool: + """Open new scene in the host application. + + Args: + force: If true, force action even if there are unsaved changes. + + Returns: + True if new scene is opened, False on error or if operation is cancelled. + """ + raise NotImplementedError() + + @abstractmethod + def open_file(self, file_path: str) -> bool: + """Open the specified file in the host application. + + Args: + file_path: Absolute path to the local file to open. + + Returns: + True if file is opened, False on error or if operation is cancelled. + """ + raise NotImplementedError() + + def save_file(self, file_path: str): + """Save the current scene to the specified file path. + + This operation may not be applicable to all hosts. + + Args: + file_path: Absolute local path to save file. + """ + pass + + def export(self, file_path: str): + """Export current scene to file path specified and file type + designated by file extension. + + This operation may not be applicable to all hosts. + + Args: + file_path: Absolute local path to export file. + """ + pass + + @abstractmethod + def dialog( + self, + title: str, + msg: str, + buttons: list[str] | None = None, + default: int = 0, + cancel: int | None = None, + no_ui_option: int | None = None, + ) -> int: + """Pop up a dialog in the host application. + + Args: + title: Title of dialog window. + msg: Message to be displayed. + buttons: List of strings denoting buttons to be added to dialog. + default: Index of default button. + cancel: Index of cancel button. + no_ui_option: If host is running without a UI, + this option will automatically be returned. + If None, use default value. + + Returns: + The index of the button selected by user. + Value of -1 indicates a dismissed dialog. + """ + raise NotImplementedError() + + @abstractmethod + def file_dialog( + self, + title: str, + starting_dir: str = "", + folder_mode: bool = False, + file_type: str = "", + multi_select: bool = False, + ) -> list[str]: + """Invoke a file dialog for selecting one or more file paths. + + Args: + title: Title of dialog. + starting_dir: Starting location of dialog. + folder_mode: If True, dialog will browse folders instead of files. + file_type: File extension to filter for. + Applicable only when browsing files. + multi_select: If True, allow multiple selection of files. + Applicable only when browsing files. + + Returns: + A list of file/directory paths. + If multi_select = False, the return value will be a list of size 1. + If user cancels, list will be empty. + """ + raise NotImplementedError() + + @abstractmethod + def copy_to_clipboard(self, text: str) -> bool: + """Copy given text to clipboard of relevant application. + Default implementation copies to system clipboard. + + Args: + text: Text to be copied. + + Returns: + True on success. + """ + raise NotImplementedError() + + def get_dependency_tree(self, must_exist: bool = True) -> DependencyData: + """Return a DependencyData object which is the root of the + dependency tree for the scene. + + Args: + must_exist: Only return dependencies that can be found on disk. + """ + # Return empty root node by default + return DependencyData() + + def update_dependency( + self, + dep: DependencyData, + file_path: str, + ) -> DependencyData: + """Update an existing dependency to point to given file in current scene. + + Args: + dep: DependencyData node which identifies the dependency to be updated. + file_path: New path to set dependency to. + + Returns: + DependencyData object describing new state of dependency. + NOTE: This will be an isolated node, not including sub-dependency info. + """ + # Do nothing by default + return dep + + def env_var_marker(self, var_name: str) -> str: + """Return the environment variable marker format for this host. + + Args: + var_name: The environment variable name. + + Returns: + Environment variable marker in host-specific format. + Default implementation returns shell format: ${VAR_NAME} + """ + return "${" + var_name + "}" diff --git a/python/tk-core/python/tank/flowam/open.py b/python/tk-core/python/tank/flowam/open.py new file mode 100644 index 00000000..490332d2 --- /dev/null +++ b/python/tk-core/python/tank/flowam/open.py @@ -0,0 +1,218 @@ +# Copyright (c) 2026 Shotgun Software Inc. +# +# CONFIDENTIAL AND PROPRIETARY +# +# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit +# Source Code License included in this distribution package. See LICENSE. +# By accessing, using, copying or modifying this work you indicate your +# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights +# not expressly granted therein are reserved by Shotgun Software Inc. + +from __future__ import annotations # needed for python 3.9 support + +import os + +import sgtk +from tank import LogManager +from tank_vendor.flow_integration_sdk.exceptions import ( + DraftExistsError, + FlowError, + InvalidDraftError, +) +from tank_vendor.flow_integration_sdk.globals import SOURCE_PURPOSE +from tank_vendor.flow_integration_sdk.objects import FlowRevision, FlowVersion +from tank_vendor.flow_integration_sdk.sandbox import ( + CheckoutDraftInfo, +) +from tank_vendor.flow_integration_sdk.sandbox import ( + checkout_revision as checkout_revision_in_sandbox, +) +from tank_vendor.flow_integration_sdk.sandbox import ( + get_draft_id, + is_local_draft, + read_draft_info, +) +from tank_vendor.flow_integration_sdk.utils import trace + +logger = LogManager.get_logger(__name__) + + +@trace +def checkout_revision( + revision_id: str, force: bool = False +) -> CheckoutDraftInfo | None: + """Check out the given asset revision/version into sandbox. + If the version is already checked out, offer an overwrite. + If a different version of the same asset is already checkout out, offer an overwrite. + + Args: + revision_id: Id of a published AM asset revision. This can also be a version id. + force: If True, force a re-checkout even if an existing draft is found. + + Returns: + CheckoutDraftInfo object or None if operation was cancelled. + + Raises: + FlowError + """ + if FlowVersion.is_version_id(revision_id): + revision = FlowVersion(revision_id).revision + else: + revision = FlowRevision.get_revision(revision_id) + + try: + draft_info = checkout_revision_in_sandbox( + revision._revision, + component_purpose=SOURCE_PURPOSE, + force=force, + ) + except DraftExistsError: + draft_info = _handle_existing_draft(revision) + if draft_info is None: + # Operation was cancelled so exit + return None + + # Open checked out draft file + flow_host = sgtk.platform.current_engine().flow_host + if flow_host: + draft_path = draft_info.source_path + logger.info(f"Opening draft path: {draft_path}") + flow_host.open_file(draft_path) + + return draft_info + + +@trace +def _handle_existing_draft(revision: FlowRevision) -> CheckoutDraftInfo | None: + """Called during a checkout operation when an existing draft is detected + to handle the situation as safely as possible. If host class is available, + pop-up a dialog to ask the user what to do, otherwise output an informative + message and cancel the operation. + + NOTE: We are only concerned with versions here and not the granularity of + revisions. This is because we are assuming that binaries and dependencies + (i.e. components and uses relationships) cannot change between revisions + of the same version. Therefore, checking out any given revision of a version + should be sufficient for ensuring that we fetch and copy the correct binaries. + + Returns CheckoutDraftInfo object if successful, or None if operation is cancelled. + """ + draft_id = get_draft_id(revision.id) + try: + draft_info = read_draft_info(draft_id) + checkout_version = draft_info.version + checkout_revision = draft_info.revision + except InvalidDraftError: + # This indicates that the draft is corrupted + checkout_version = None + checkout_revision = None + + # If not host is available, just cancel the operation + flow_host = sgtk.platform.current_engine().flow_host + if flow_host is None: + msg = "There is an existing draft detected with checkout version: " + msg += f"{checkout_version}." + logger.error(msg) + return None + + title = "Existing draft detected" + if checkout_version is None: + # Warning to user with options to + # 0 - overwrite existing draft + # 1 - cancel + msg = "There is an existing draft which is corrupted. " + msg += "Would you like to overwrite this with a fresh checkout?" + options = ["New checkout", "Cancel"] + + action_idx = flow_host.dialog( + title=title, + msg=msg, + buttons=options, + default=1, # cancel by default + no_ui_option=1, # cancel when no dialog can be launched + ) + action = options[action_idx] + + elif checkout_version == revision.version_number: + # Warning to user with options to + # 0 - overwrite existing draft + # 1 - proceed with existing draft + msg = "This version is already checked out in sandbox. " + msg += "Would you like to overwrite this with a fresh checkout?" + options = ["New checkout", "Use original checkout"] + + action_idx = flow_host.dialog( + title=title, + msg=msg, + buttons=options, + default=1, # use original by default + no_ui_option=1, # use original when no dialog can be launched + ) + action = options[action_idx] + + else: + # Warning to user with options to + # 1 - overwrite existing draft + # 2 - cancel + msg = f"An existing checkout already exists of version {checkout_version} " + msg += f"(r{checkout_revision}) of this asset. Would you like to overwrite " + msg += f"this with a checkout of version {revision.version_number} " + msg += f"(r{revision.revision_number})?" + options = ["New checkout", "Cancel"] + + action_idx = flow_host.dialog( + title=title, + msg=msg, + buttons=options, + default=1, # cancel by default + no_ui_option=1, # cancel when no dialog can be launched + ) + action = options[action_idx] + + if action == "New checkout": + # Force a new checkout + return checkout_revision_in_sandbox( + revision._revision, + component_purpose=SOURCE_PURPOSE, + force=True, + ) + elif action == "Use original checkout": + msg = f"Using original draft of version {checkout_version} " + msg += f"(r{checkout_revision})." + logger.info(msg) + return draft_info + else: + logger.warning("Checkout operation cancelled.") + return None + + +@trace +def open_draft(draft_id: str): + """Open draft source file for editing if draft is local. + + Args: + draft_id: Unique id that identifies a draft location in local sandbox. + + Raises: + FlowError + InvalidDraftError + """ + flow_host = sgtk.platform.current_engine().flow_host + if not flow_host: + raise FlowError( + "Opening a draft must be done in an engine which supports Flow integration." + ) + + if not is_local_draft(draft_id): + msg = f'The draft "{draft_id}" is not in local sandbox.' + raise InvalidDraftError(draft_id=draft_id, details=msg) + + draft_info = read_draft_info(draft_id) + draft_path = draft_info.source_path + if not os.path.exists(draft_path): + msg = f'Corrupted draft folder. The file "{draft_path}" does not exist.' + raise InvalidDraftError(draft_id=draft_id, details=msg) + + # Open file + logger.info(f"Opening file: {draft_path}") + flow_host.open_file(draft_path) diff --git a/python/tk-core/python/tank/flowam/utils.py b/python/tk-core/python/tank/flowam/utils.py new file mode 100644 index 00000000..38092202 --- /dev/null +++ b/python/tk-core/python/tank/flowam/utils.py @@ -0,0 +1,304 @@ +# Copyright (c) 2026 Shotgun Software Inc. +# +# CONFIDENTIAL AND PROPRIETARY +# +# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit +# Source Code License included in this distribution package. See LICENSE. +# By accessing, using, copying or modifying this work you indicate your +# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights +# not expressly granted therein are reserved by Shotgun Software Inc. + +"""Shared utilities for Flow Integration.""" + +from __future__ import annotations # needed for python 3.9 support + +import glob +import os +import re +import webbrowser +from dataclasses import asdict, dataclass +from typing import TYPE_CHECKING + +import fileseq +from tank import LogManager +from tank.authentication import flow_auth +from tank.pipelineconfig import PipelineConfiguration +from tank.util import yaml_cache + +if TYPE_CHECKING: + from tank.context import Context + +from tank_vendor.flow_integration_sdk import globals, schema, storage +from tank_vendor.flow_integration_sdk.exceptions import FlowError +from tank_vendor.flow_integration_sdk.objects import FlowProject +from tank_vendor.flow_integration_sdk.publish import ( + CommentComponentSpec, + ComponentSpec, + FileSeqComponentSpec, + SourceComponentSpec, + ThumbnailComponentSpec, + TypeComponentSpec, +) +from tank_vendor.flow_integration_sdk.schema_builder import create_pipeline_schemas +from tank_vendor.flow_integration_sdk.utils import trace + +from .constants import FLOW_SCHEMA_CONFIG_PATH, FLOW_SCHEMA_VERSION_FIELD + +logger = LogManager.get_logger(__name__) + + +@dataclass +class BaseInputs: + def format_str(self, tab: int = 0): + s = "" + for prop, value in self.__dict__.items(): + s += "\t" * tab + f"{prop} = {value}\n" + return s.rstrip() + + def validate(self): + """Do validation of input values.""" + pass + + def asdict(self): + return asdict(self) + + def log_intro(self, intro_message: str): + """Log an intro message along with the values of + input object. + """ + + msg = f""" + --------------------------------------- + {intro_message} with inputs: +{self.format_str(tab=3)} + --------------------------------------""" + logger.info(msg) + + +def get_config_flow_settings(pipeline_config: PipelineConfiguration) -> dict: + """Retrieve Flow settings from config. + + Args: + pipeline_config: PipelineConfiguration object. + This is used to determine the location from which to + search for the "flow.yml" config file that contains Flow settings. + Config file will be searched in two locations: + * {CONFIG ROOT}/config/core/flow.yml + * {CONFIG ROOT}/core/flow.yml + + Returns: + Dictionary result of reading the "flow.yml" config file if found. + Otherwise, return empty dictionary. + """ + config_root = pipeline_config.get_config_location() + if not config_root: + logger.error("Pipeline config has no config location.") + return {} + + # Config path if reading from cached config + override_path_cache = os.path.join( + config_root, + "config", + "core", + "flow.yml", + ) + # Config path if reading form dev descriptor + override_path_dev = os.path.join( + config_root, + "core", + "flow.yml", + ) + + logger.info(f"Checking for Flow config: {override_path_dev}") + if os.path.exists(override_path_dev): + return yaml_cache.g_yaml_cache.get(override_path_dev) or {} + else: + logger.info(f"Checking for Flow config: {override_path_cache}") + if os.path.exists(override_path_cache): + return yaml_cache.g_yaml_cache.get(override_path_cache) or {} + else: + logger.error("Flow config could not be found!") + return {} + + +def init_flow( + pipeline_config: PipelineConfiguration, + sg_connection, + context: Context, +): + """Do session set up + schema provisioning for the Flow Integration SDK. + + Args: + pipeline_config: PipelineConfiguration object, used to read flow + settings from config. + sg_connection: Shotgun connection, used to write the schema config + version back to SG. + context: The current Toolkit context, used to read the flow project id, + schema version, and SG project id. + + Raises: + RuntimeError + """ + logger.info("Doing Flow Integration SDK initialization...") + flow_project_id = context.flow_project_id + sg_project_id = context.project["id"] + logger.info(f"Flow AM Project ID: {flow_project_id}") + + # Read flow settings from config + settings = get_config_flow_settings(pipeline_config) + flow_endpoint = settings.get("endpoint") + flow_web_url = settings.get("web_url") + flow_sandbox_root = settings.get("sandbox_root") + flow_storage_root = settings.get("storage_root") + + # Configure logger + globals.set_logger_callback(LogManager().get_logger) + # Initialize MEDM GQL client + globals.init_client(flow_endpoint, flow_auth.FlowAuthenticationHandler()) + # Store web url + globals.set_webapp_url(flow_web_url) + # Set session collection + try: + project = FlowProject(flow_project_id) + except FlowError as exc: + msg = f"Could not complete Flow initialization: {exc}" + raise RuntimeError(msg) from exc + globals.init_session_collection( + project.collection_id, project.organization_id, project.group_id + ) + # Cache custom schema config + try: + schema.cache_schema_config(FLOW_SCHEMA_CONFIG_PATH) + except (RuntimeError, ValueError) as exc: + msg = "Could not complete Flow initialization: {exc}" + raise RuntimeError(msg) from exc + # Configure storage roots + storage.set_sandbox_root(flow_sandbox_root, create_dir=True) + storage.set_storage_root(flow_storage_root, create_dir=True) + + # Provision pipeline schemas for CPA collections only + session_collection = globals.get_session_collection() + if not session_collection.is_cpa_collection(): + logger.info("Skipping pipeline schema provisioning - not a CPA collection.") + else: + current_version = schema.get_schema_config_version(FLOW_SCHEMA_CONFIG_PATH) + if context.flow_schema_version == current_version: + logger.info( + f"Schema config version {current_version} matches. " + "Skipping schema provisioning." + ) + else: + try: + create_pipeline_schemas( + project_id=flow_project_id, + config_path=FLOW_SCHEMA_CONFIG_PATH, + ) + sg_connection.update( + "Project", + sg_project_id, + {FLOW_SCHEMA_VERSION_FIELD: current_version}, + ) + except ( + FlowError, + RuntimeError, + ValueError, + KeyError, + FileNotFoundError, + ) as exc: + msg = f"Could not complete Flow schema provisioning: {exc}" + raise RuntimeError(msg) from exc + + logger.info("Initialization complete!") + + +@trace +def create_components_for_publish( + source_paths: list[str] | None = None, + thumbnail_path: str = "", + comment: str = "", + type_ids: list[str] | None = None, +) -> list[ComponentSpec]: + """Generate the components relevant to publish a new revision. + + Args: + source_paths: Optional list of local paths to the source files. + thumbnail_path: Optional path to thumbnail file. + comment: Generate a comment component with given comment string. + Should only be included if the publish method does not + explicitly accept a `comment` parameter. + type_ids: A list of type ids to be converted into type components. + This is only relevant if publishing a new asset direct to remote + (i.e. not going through sandbox). + """ + # Source component contains the source file + components: list[ComponentSpec] = [] + if source_paths: + source_comp = SourceComponentSpec(*source_paths) + components.append(source_comp) + # Thumbnail component contains the thumbnail file + if thumbnail_path: + thumbnail_comp = ThumbnailComponentSpec(thumbnail_path) + components.append(thumbnail_comp) + # Need to add a special file sequence type component if there are + # multiple source files + if source_paths and len(source_paths) > 1: + sequences = fileseq.findSequencesInList(source_paths) + if len(sequences) > 1: + msg = "Ambiguous file sequence provided for publish." + msg += " Multiple file sequences have been detected in the" + msg += " source path list. Please input files with common" + msg += " base name with frame padding (e.g. .####.)." + raise FlowError(msg) + # Determine file format + pad_len = sequences[0].getPaddingNum(sequences[0].padding()) + frame_expr = f"%0{pad_len}d" + file_format = os.path.basename(sequences[0].frame(frame_expr)) + components.append( + FileSeqComponentSpec( + type_id=schema.get_schema_id(globals.FILE_SEQ_TYPE), + frame_start=sequences[0].start(), + frame_end=sequences[0].end(), + frame_set=str(sequences[0].frameSet()), + file_format=file_format, + name=globals.FILE_SEQ_COMP, + ) + ) + # Add comment if provided + if comment: + comment_comp = CommentComponentSpec(comment=comment) + components.append(comment_comp) + # Add type components if specified + type_ids = [] if type_ids is None else type_ids + for i, type_id in enumerate(type_ids): + # NOTE: component names must be unique! + type_comp = TypeComponentSpec(type_id=type_id, name=f"Type {i}") + components.append(type_comp) + return components + + +def open_explorer(dir_path: str): + """Open a file explorer to the directory path provided. + + Args: + dir_path: Full path to local directory. + """ + return webbrowser.open(f"file:///{dir_path}") + + +def search_file_expression(file_path: str): + """Search file system for files matching expression that + uses frame padding of the format "%0Nd" where N is an integer + denoting number of digits in frame padding. + + If path does not contain a frame padding, search for the path as is. + """ + expr = r"(?P.+\.)%0(?P\d)d(?P\..+)" + m = re.match(expr, file_path) + if m: + frame_pad = int(m.group("frame_pad")) + file_expr = m.group("base_path") + frame_pad * "[0-9]" + m.group("ext") + file_list = glob.glob(file_expr) + return file_list + elif os.path.exists(file_path): + return [file_path] + return [] diff --git a/python/tk-core/python/tank/folder/__init__.py b/python/tk-core/python/tank/folder/__init__.py index 26d2fe81..63352810 100644 --- a/python/tk-core/python/tank/folder/__init__.py +++ b/python/tk-core/python/tank/folder/__init__.py @@ -13,5 +13,5 @@ """ -from .operations import process_filesystem_structure, synchronize_folders from .configuration import read_ignore_files +from .operations import process_filesystem_structure, synchronize_folders diff --git a/python/tk-core/python/tank/folder/configuration.py b/python/tk-core/python/tank/folder/configuration.py index dfc9d369..f79811c8 100644 --- a/python/tk-core/python/tank/folder/configuration.py +++ b/python/tk-core/python/tank/folder/configuration.py @@ -13,22 +13,21 @@ """ -import os import fnmatch +import os +from ..errors import TankError, TankUnreadableFileError +from ..util import yaml_cache from .folder_types import ( - Static, - ListField, Entity, + ListField, Project, - UserWorkspace, ShotgunStep, ShotgunTask, + Static, + UserWorkspace, ) -from ..errors import TankError, TankUnreadableFileError -from ..util import yaml_cache - def read_ignore_files(schema_config_path): """ @@ -336,7 +335,7 @@ def _process_config_r(self, parent_node, parent_path): self._process_config_r(cur_node, full_path) # process symlinks - for (path, target, metadata) in self._get_symlinks_in_folder(parent_path): + for path, target, metadata in self._get_symlinks_in_folder(parent_path): parent_node.add_symlink(path, target, metadata) # now process all files and add them to the parent_node token diff --git a/python/tk-core/python/tank/folder/folder_io.py b/python/tk-core/python/tank/folder/folder_io.py index 96381d82..8b7c1bf1 100644 --- a/python/tk-core/python/tank/folder/folder_io.py +++ b/python/tk-core/python/tank/folder/folder_io.py @@ -16,10 +16,9 @@ """ -from . import constants from ..errors import TankError - from ..path_cache import PathCache +from . import constants class FolderIOReceiver(object): diff --git a/python/tk-core/python/tank/folder/folder_types/__init__.py b/python/tk-core/python/tank/folder/folder_types/__init__.py index 7b4124ec..27d05683 100644 --- a/python/tk-core/python/tank/folder/folder_types/__init__.py +++ b/python/tk-core/python/tank/folder/folder_types/__init__.py @@ -13,11 +13,11 @@ folder types that can be created. """ +from .entity import Entity from .errors import EntityLinkTypeMismatch -from .static import Static from .listfield import ListField -from .entity import Entity from .project import Project -from .user import UserWorkspace +from .static import Static from .step import ShotgunStep from .task import ShotgunTask +from .user import UserWorkspace diff --git a/python/tk-core/python/tank/folder/folder_types/base.py b/python/tk-core/python/tank/folder/folder_types/base.py index ac84ca82..ca87ba25 100644 --- a/python/tk-core/python/tank/folder/folder_types/base.py +++ b/python/tk-core/python/tank/folder/folder_types/base.py @@ -8,8 +8,8 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -import os import copy +import os from .expression_tokens import SymlinkToken @@ -174,9 +174,9 @@ def create_folders( # before recursing down our specific recursion path, make sure all static content # has been created at this level in the folder structure - static_children = [ch for ch in self._children if ch.is_dynamic() == False] + static_children = [ch for ch in self._children if ch.is_dynamic() is False] - for (created_folder, sg_data_dict) in created_data: + for created_folder, sg_data_dict in created_data: # first process the static folders for cp in static_children: @@ -208,7 +208,7 @@ def create_folders( # no explicit list! instead process all children. # run the folder creation for all new folders created and for all # configuration children - for (created_folder, sg_data_dict) in created_data: + for created_folder, sg_data_dict in created_data: for cp in self._children: cp.create_folders( io_receiver, @@ -255,7 +255,7 @@ def _should_item_be_processed(self, engine_str, is_primary): dc_value = self._config_metadata.get("defer_creation") # if defer_creation config param not specified or None means we # can always go ahead with folder creation!! - if dc_value is None or dc_value == False: + if dc_value is None or dc_value is False: # deferred value not specified means proceed with creation! return True @@ -266,7 +266,7 @@ def _should_item_be_processed(self, engine_str, is_primary): return False # now handle all cases where we have an engine_str and some sort of deferred behaviour. - if dc_value == True: + if dc_value is True: # defer create for all engine_strs! return True diff --git a/python/tk-core/python/tank/folder/folder_types/entity.py b/python/tk-core/python/tank/folder/folder_types/entity.py index cd0a5b4b..97fb7735 100644 --- a/python/tk-core/python/tank/folder/folder_types/entity.py +++ b/python/tk-core/python/tank/folder/folder_types/entity.py @@ -8,16 +8,15 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -import os import copy +import os from ...errors import TankError from ...util import shotgun_entity - -from .errors import EntityLinkTypeMismatch from .base import Folder +from .errors import EntityLinkTypeMismatch from .expression_tokens import FilterExpressionToken -from .util import translate_filter_tokens, resolve_shotgun_filters +from .util import resolve_shotgun_filters, translate_filter_tokens class Entity(Folder): @@ -117,7 +116,7 @@ def _should_item_be_processed(self, engine_str, is_primary): """ # check our special condition - is this node set to be auto-created with its parent node? # note that primary nodes are always created with their parent nodes! - if is_primary == False and self._create_with_parent == False: + if is_primary is False and self._create_with_parent is False: return False # base class implementation diff --git a/python/tk-core/python/tank/folder/folder_types/expression_tokens.py b/python/tk-core/python/tank/folder/folder_types/expression_tokens.py index 1a3d1a3e..a7d7ef2e 100644 --- a/python/tk-core/python/tank/folder/folder_types/expression_tokens.py +++ b/python/tk-core/python/tank/folder/folder_types/expression_tokens.py @@ -59,7 +59,7 @@ def resolve_token(self, folder_obj, sg_data): # - listfield tokens contain the value # - sg entity values contain the value in a compute_name key name_value = None - for (field_name, field_value) in sg_data.items(): + for field_name, field_value in sg_data.items(): if token == field_name: if isinstance(field_value, dict): diff --git a/python/tk-core/python/tank/folder/folder_types/listfield.py b/python/tk-core/python/tank/folder/folder_types/listfield.py index 57ccf0a0..62647253 100644 --- a/python/tk-core/python/tank/folder/folder_types/listfield.py +++ b/python/tk-core/python/tank/folder/folder_types/listfield.py @@ -8,12 +8,11 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -import os import copy +import os from ...errors import TankError from ...util import shotgun_entity - from .base import Folder from .expression_tokens import FilterExpressionToken @@ -106,7 +105,7 @@ def _should_item_be_processed(self, engine_str, is_primary): # list fields are only created when they are on the primary path, # e.g. we don't recurse down to create asset types when shots are created, # but only when assets are created. - if is_primary == False and self._create_with_parent == False: + if is_primary is False and self._create_with_parent is False: return False # base class implementation @@ -172,7 +171,7 @@ def _create_folders_impl(self, io_receiver, parent_path, sg_data): chunks = self._field_name.split(".") entity_type = chunks[1] field_name = chunks[2] - except: + except Exception: msg = ( "Folder creation error: Cannot resolve the field expression %s." % self._field_name @@ -190,7 +189,9 @@ def _create_folders_impl(self, io_receiver, parent_path, sg_data): # validate that the data type is of type list field_type = resp[field_name]["data_type"]["value"] except Exception as e: - msg = "Folder creation error: Cannot retrieve values for PTR list field " + msg = ( + "Folder creation error: Cannot retrieve values for PTR list field " + ) msg += "%s.%s. Error reported: %s" % (entity_type, field_name, e) raise TankError(msg) diff --git a/python/tk-core/python/tank/folder/folder_types/project.py b/python/tk-core/python/tank/folder/folder_types/project.py index bb472a0a..9bbb6b51 100644 --- a/python/tk-core/python/tank/folder/folder_types/project.py +++ b/python/tk-core/python/tank/folder/folder_types/project.py @@ -8,10 +8,10 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -from ...errors import TankError -from .entity import Entity from ... import constants +from ...errors import TankError from ...log import LogManager +from .entity import Entity log = LogManager.get_logger(__name__) diff --git a/python/tk-core/python/tank/folder/folder_types/static.py b/python/tk-core/python/tank/folder/folder_types/static.py index 41d85157..79c64dd2 100644 --- a/python/tk-core/python/tank/folder/folder_types/static.py +++ b/python/tk-core/python/tank/folder/folder_types/static.py @@ -11,10 +11,9 @@ import os from ...errors import TankError - from .base import Folder from .entity import Entity -from .util import translate_filter_tokens, resolve_shotgun_filters +from .util import resolve_shotgun_filters, translate_filter_tokens class Static(Folder): @@ -136,7 +135,7 @@ def _should_item_be_processed(self, engine_str, is_primary): """ # check our special condition - is this node set to be auto-created with its parent node? # note that primary nodes are always created with their parent nodes! - if is_primary == False and self._create_with_parent == False: + if is_primary is False and self._create_with_parent is False: return False # base class implementation diff --git a/python/tk-core/python/tank/folder/folder_types/step.py b/python/tk-core/python/tank/folder/folder_types/step.py index f22f6dbf..733dc206 100644 --- a/python/tk-core/python/tank/folder/folder_types/step.py +++ b/python/tk-core/python/tank/folder/folder_types/step.py @@ -11,10 +11,9 @@ import os from ...errors import TankError - from .entity import Entity +from .expression_tokens import CurrentStepExpressionToken, FilterExpressionToken from .util import translate_filter_tokens -from .expression_tokens import FilterExpressionToken, CurrentStepExpressionToken class ShotgunStep(Entity): @@ -148,7 +147,7 @@ def __init__( # shot, we want all the steps to be created at the same time. # however, if we have create_with_parent set to False, we only want to create # this node if we are creating folders for a task. - if create_with_parent != True: + if create_with_parent is False: # do not auto-create with parent - only create when a task has been specified. # create an expression object to represent the current step. # we pass in the field which is the connection between the task and the step field diff --git a/python/tk-core/python/tank/folder/folder_types/task.py b/python/tk-core/python/tank/folder/folder_types/task.py index c4f6daaf..1840c8ec 100644 --- a/python/tk-core/python/tank/folder/folder_types/task.py +++ b/python/tk-core/python/tank/folder/folder_types/task.py @@ -11,11 +11,10 @@ import os from ...errors import TankError - from .entity import Entity -from .util import translate_filter_tokens -from .expression_tokens import FilterExpressionToken, CurrentTaskExpressionToken +from .expression_tokens import CurrentTaskExpressionToken, FilterExpressionToken from .step import ShotgunStep +from .util import translate_filter_tokens class ShotgunTask(Entity): @@ -160,7 +159,7 @@ def __init__( # shot, we want all the tasks to be created at the same time. # however, if we have create_with_client set to False, we only want to create # this node if we are creating folders for a task. - if create_with_parent != True: + if create_with_parent is False: # do not auto-create with parent - only create when a task has been specified. # create an expression object to represent the current step current_task_id_token = CurrentTaskExpressionToken() diff --git a/python/tk-core/python/tank/folder/folder_types/user.py b/python/tk-core/python/tank/folder/folder_types/user.py index cdd27589..e4c8f192 100644 --- a/python/tk-core/python/tank/folder/folder_types/user.py +++ b/python/tk-core/python/tank/folder/folder_types/user.py @@ -8,9 +8,8 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -from ...util import login from ...errors import TankError - +from ...util import login from .entity import Entity from .util import translate_filter_tokens diff --git a/python/tk-core/python/tank/folder/folder_types/util.py b/python/tk-core/python/tank/folder/folder_types/util.py index 8e435665..f7873d70 100644 --- a/python/tk-core/python/tank/folder/folder_types/util.py +++ b/python/tk-core/python/tank/folder/folder_types/util.py @@ -15,11 +15,10 @@ import copy from ...errors import TankError - from .expression_tokens import ( - FilterExpressionToken, CurrentStepExpressionToken, CurrentTaskExpressionToken, + FilterExpressionToken, ) diff --git a/python/tk-core/python/tank/folder/operations.py b/python/tk-core/python/tank/folder/operations.py index 506e2a2c..dfa29cae 100644 --- a/python/tk-core/python/tank/folder/operations.py +++ b/python/tk-core/python/tank/folder/operations.py @@ -13,10 +13,10 @@ """ +from ..errors import TankError from .configuration import FolderConfiguration from .folder_io import FolderIOReceiver from .folder_types import EntityLinkTypeMismatch -from ..errors import TankError def create_single_folder_item( diff --git a/python/tk-core/python/tank/hook.py b/python/tk-core/python/tank/hook.py index 12b2502e..76eb3807 100644 --- a/python/tk-core/python/tank/hook.py +++ b/python/tk-core/python/tank/hook.py @@ -11,18 +11,20 @@ """ Defines the base class for all Tank Hooks. """ + +import inspect +import logging import os import sys -import logging -import inspect import threading -from .util.loader import load_plugin + from . import LogManager from .errors import ( TankError, TankFileDoesNotExistError, TankHookMethodDoesNotExistError, ) +from .util.loader import load_plugin log = LogManager.get_logger(__name__) @@ -447,7 +449,7 @@ def some_method(self): try: engine = self.__parent.engine - except: + except Exception: raise TankError( "Cannot load framework %s for %r - it does not have a " "valid engine property!" % (framework_instance_name, self.__parent) diff --git a/python/tk-core/python/tank/log.py b/python/tk-core/python/tank/log.py index 012a5962..87f5363a 100644 --- a/python/tk-core/python/tank/log.py +++ b/python/tk-core/python/tank/log.py @@ -220,15 +220,14 @@ def my_method(): For more information, see https://docs.python.org/2/library/logging.handlers.html#module-logging.handlers """ - import logging -from logging.handlers import RotatingFileHandler import os -import sys import time -import weakref import uuid +import weakref from functools import wraps +from logging.handlers import RotatingFileHandler + from . import constants @@ -303,7 +302,7 @@ def doRollover(self): try: os.rename(self.baseFilename, temp_backup_name) - except: + except Exception: # It failed, so we'll simply append from now on. log.debug( "Cannot rotate log file '%s'. Logging will continue to this file, " @@ -318,7 +317,7 @@ def doRollover(self): # so doRollover can do its work. try: os.rename(temp_backup_name, self.baseFilename) - except: + except Exception: # For some reason we couldn't move the backup in its place. log.debug( "Unexpected issue while rotating log file '%s'. Logging will continue to this file, " @@ -338,7 +337,7 @@ def doRollover(self): # disable rollover and append to the current log. try: RotatingFileHandler.doRollover(self) - except: + except Exception: # Something probably failed trying to rollover the backups, # since the code above proved that in theory the main log file # should be renamable. In any case, we didn't succeed in renaming, @@ -595,7 +594,7 @@ def _get_global_debug(self): @property def log_file(self): - """ Full path to the current log file or None if logging is not active. """ + """Full path to the current log file or None if logging is not active.""" return self._std_file_handler_log_file @property @@ -832,6 +831,8 @@ def initialize_base_file_handler_from_path(self, log_file): # this should not be changed, but any filtering # should happen via log handlers sgtk_root_logger.setLevel(logging.DEBUG) + + # # create a 'nop' log handler to be attached. # this is to avoid warnings being reported that diff --git a/python/tk-core/python/tank/path_cache.py b/python/tk-core/python/tank/path_cache.py index 600893c1..70264edf 100644 --- a/python/tk-core/python/tank/path_cache.py +++ b/python/tk-core/python/tank/path_cache.py @@ -15,16 +15,15 @@ """ import collections -import sqlite3 -import sys -import os import itertools import json +import os +import sqlite3 +import sys -from .platform.engine import show_global_busy, clear_global_busy -from . import constants +from . import LogManager, constants from .errors import TankError -from . import LogManager +from .platform.engine import clear_global_busy, show_global_busy from .util.login import get_current_user # Shotgun field definitions to store the path cache data @@ -115,8 +114,7 @@ def _init_db(self): # up the default page size somewhat (from 4k -> 8k) to improve # performance. See https://sqlite.org/pragma.html#pragma_page_size - c.executescript( - """ + c.executescript(""" PRAGMA page_size=8192; CREATE TABLE path_cache (entity_type text, entity_id integer, entity_name text, root text, path text, primary_entity integer); @@ -134,8 +132,7 @@ def _init_db(self): CREATE UNIQUE INDEX shotgun_status_id ON shotgun_status(path_cache_id); CREATE INDEX shotgun_status_shotgun_id ON shotgun_status(shotgun_id); - """ - ) + """) self._connection.commit() else: @@ -160,8 +157,7 @@ def _init_db(self): # check for primary entity field - this was added back in 0.12.x if "primary_entity" not in field_names: - c.executescript( - """ + c.executescript(""" ALTER TABLE path_cache ADD COLUMN primary_entity integer; UPDATE path_cache SET primary_entity=1; @@ -170,8 +166,7 @@ def _init_db(self): DROP INDEX IF EXISTS path_cache_all; CREATE UNIQUE INDEX IF NOT EXISTS path_cache_all ON path_cache(entity_type, entity_id, root, path, primary_entity); - """ - ) + """) self._connection.commit() @@ -501,7 +496,10 @@ def _upload_cache_data_to_shotgun(self, data, event_log_desc): sg_batch_data.append(req) # push to shotgun in a single xact - log.debug("Uploading %s path entries to Flow Production Tracking..." % len(sg_batch_data)) + log.debug( + "Uploading %s path entries to Flow Production Tracking..." + % len(sg_batch_data) + ) try: response = self._tk.shotgun.batch(sg_batch_data) @@ -513,14 +511,23 @@ def _upload_cache_data_to_shotgun(self, data, event_log_desc): # now create a dictionary where input path cache rowid (path_cache_row_id) # is mapped to the shotgun ids that were just created + def _normalize_path(p): + # Local cache paths use os.sep (backslashes on Windows, per + # _sanitize_path) while paths round-tripped through Shotgun's + # local_path field come back with forward slashes. normpath + # unifies separators and collapses redundant components; + # normcase lowercases on case-insensitive filesystems (Windows). + return os.path.normcase(os.path.normpath(p)) if p else p + def _rowid_from_filesystem_entity(fsl_entity): path = fsl_entity[SG_PATH_FIELD]["local_path"] + normalized_path = _normalize_path(path) for d in data: # We need to match not only the path but also the entity type when associating the FilesystemLocation # entities with the local cache's row ids, because Task folders generate two entries with the same # path, one for the Task and one for the Step. if ( - d["path"] == path + _normalize_path(d["path"]) == normalized_path and d["entity"]["type"] == fsl_entity["linked_entity_type"] ): return d["path_cache_row_id"] @@ -896,7 +903,9 @@ def _replay_folder_entities(self, cursor, max_event_log_id): - path """ - log.debug("Fetching already registered folders from Flow Production Tracking...") + log.debug( + "Fetching already registered folders from Flow Production Tracking..." + ) sg_data = self._get_filesystem_location_entities(folder_ids=None) @@ -1320,19 +1329,19 @@ def add_mappings(self, data, entity_type, entity_ids): ) # now push to shotgun - (event_log_id, sg_id_lookup) = self._upload_cache_data_to_shotgun( + event_log_id, sg_id_lookup = self._upload_cache_data_to_shotgun( data_for_sg, desc ) self._update_last_event_log_synced(c, event_log_id) # and indicate in the path cache that all these records have been pushed - for (pc_row_id, sg_id) in sg_id_lookup.items(): + for pc_row_id, sg_id in sg_id_lookup.items(): c.execute( "INSERT INTO shotgun_status(path_cache_id, shotgun_id) " "VALUES(?, ?)", (pc_row_id, sg_id), ) - except: + except Exception: # error processing shotgun. Make sure we roll back the sqlite path cache # transaction self._connection.rollback() @@ -1736,7 +1745,9 @@ def ensure_all_entries_are_in_shotgun(self): SG_BATCH_SIZE = 50 log.info("") - log.info("Step 1 - Downloading current path data from Flow Production Tracking...") + log.info( + "Step 1 - Downloading current path data from Flow Production Tracking..." + ) sg_data = self._tk.shotgun.find( SHOTGUN_ENTITY, @@ -1764,18 +1775,14 @@ def ensure_all_entries_are_in_shotgun(self): try: # get all records and check each one against shotgun. - pc_data = list( - cursor.execute( - """select pc.rowid, + pc_data = list(cursor.execute("""select pc.rowid, pc.entity_type, pc.entity_id, pc.entity_name, pc.root, pc.path, pc.primary_entity - from path_cache pc""" - ) - ) + from path_cache pc""")) finally: cursor.close() @@ -1849,11 +1856,13 @@ def ensure_all_entries_are_in_shotgun(self): ids_to_look_for[sg_record["entity"]["type"]].append(sg_record) # now query shotgun for each of the types - ids_in_shotgun = {} sg_valid_records = [] - for (et, sg_records_for_et) in ids_to_look_for.items(): + for et, sg_records_for_et in ids_to_look_for.items(): - log.info(" - Checking %s %ss in Flow Production Tracking..." % (len(sg_records_for_et), et)) + log.info( + " - Checking %s %ss in Flow Production Tracking..." + % (len(sg_records_for_et), et) + ) # get the ids from shotgun for the current et. sg_ids = [x["entity"]["id"] for x in sg_records_for_et] @@ -1889,5 +1898,6 @@ def ensure_all_entries_are_in_shotgun(self): log.info("") log.info( - "Migration complete. %s records created in Flow Production Tracking" % len(sg_valid_records) + "Migration complete. %s records created in Flow Production Tracking" + % len(sg_valid_records) ) diff --git a/python/tk-core/python/tank/pipelineconfig.py b/python/tk-core/python/tank/pipelineconfig.py index d90531a1..9656faa3 100644 --- a/python/tk-core/python/tank/pipelineconfig.py +++ b/python/tk-core/python/tank/pipelineconfig.py @@ -12,26 +12,20 @@ Encapsulates the pipeline configuration and helps navigate and resolve paths across storages, configurations etc. """ -import os + import glob +import os import pickle from tank_vendor import yaml +from . import LogManager, constants, hook, pipelineconfig_utils, template_includes +from .descriptor import Descriptor, create_descriptor, descriptor_uri_to_dict from .errors import TankError, TankUnreadableFileError -from .util.version import is_version_older -from . import constants from .platform.environment import InstalledEnvironment, WritableEnvironment -from .util import shotgun, yaml_cache -from .util import ShotgunPath -from .util import StorageRoots +from .util import ShotgunPath, StorageRoots, shotgun, yaml_cache from .util.pickle import retrieve_env_var_pickled -from . import hook -from . import pipelineconfig_utils -from . import template_includes -from . import LogManager - -from .descriptor import Descriptor, create_descriptor, descriptor_uri_to_dict +from .util.version import is_version_older log = LogManager.get_logger(__name__) @@ -664,7 +658,7 @@ def get_local_storage_for_root(self, root_name): return None # get the storage data for required roots - (mapped_roots, unmapped_roots) = self.get_local_storage_mapping() + mapped_roots, unmapped_roots = self.get_local_storage_mapping() if root_name in mapped_roots: return mapped_roots[root_name] @@ -1127,7 +1121,7 @@ def get_environments(self): env_names = [] for f in glob.glob(self.get_environment_path("*")): file_name = os.path.basename(f) - (name, _) = os.path.splitext(file_name) + name, _ = os.path.splitext(file_name) env_names.append(name) return env_names @@ -1215,7 +1209,7 @@ def execute_core_hook_internal(self, hook_name, parent, **kwargs): try: return_value = hook.execute_hook(hook_path, parent, **kwargs) - except: + except Exception: # log the full callstack to make sure that whatever the # calling code is doing, this error is logged to help # with troubleshooting and support @@ -1249,9 +1243,6 @@ def execute_core_hook_method_internal( ) hook_paths = [os.path.join(hooks_path, file_name)] - # the hook.method display name used when logging the metric - hook_method_display = "%s.%s" % (hook_name, method_name) - # now add a custom hook if that exists. hook_folder = self.get_core_hooks_location() hook_path = os.path.join(hook_folder, file_name) @@ -1262,7 +1253,7 @@ def execute_core_hook_method_internal( return_value = hook.execute_hook_method( hook_paths, parent, method_name, **kwargs ) - except: + except Exception: # log the full callstack to make sure that whatever the # calling code is doing, this error is logged to help # with troubleshooting and support diff --git a/python/tk-core/python/tank/pipelineconfig_factory.py b/python/tk-core/python/tank/pipelineconfig_factory.py index 7f61f4e7..02ed1d54 100644 --- a/python/tk-core/python/tank/pipelineconfig_factory.py +++ b/python/tk-core/python/tank/pipelineconfig_factory.py @@ -8,21 +8,14 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -import os import collections +import os import pprint - +from . import LogManager, constants, pipelineconfig_utils from .errors import TankError, TankInitError -from . import LogManager -from .util import shotgun -from .util import pickle -from .util import filesystem -from .util import ShotgunPath -from . import constants -from . import pipelineconfig_utils from .pipelineconfig import PipelineConfiguration -from .util import LocalFileStorageManager +from .util import LocalFileStorageManager, ShotgunPath, filesystem, pickle, shotgun log = LogManager.get_logger(__name__) @@ -184,7 +177,8 @@ def _from_path(path, force_reread_shotgun_cache): pc_registered_path = pipelineconfig_utils.get_config_install_location(path) log.debug( - "Resolved the official path registered in PTR to be %s." % pc_registered_path + "Resolved the official path registered in PTR to be %s." + % pc_registered_path ) if pc_registered_path is None: @@ -259,7 +253,7 @@ def _validate_and_create_pipeline_configuration(associated_pipeline_configs, sou """ # extract path data from the pipeline configuration shotgun data # this will return lists of dicts with keys ``id``, (local os) ``path`` and ``project_id`` - (all_pc_data, primary_pc_data) = _get_pipeline_configuration_data( + all_pc_data, primary_pc_data = _get_pipeline_configuration_data( associated_pipeline_configs ) diff --git a/python/tk-core/python/tank/pipelineconfig_utils.py b/python/tk-core/python/tank/pipelineconfig_utils.py index 7a5543dc..38819681 100644 --- a/python/tk-core/python/tank/pipelineconfig_utils.py +++ b/python/tk-core/python/tank/pipelineconfig_utils.py @@ -13,19 +13,15 @@ across storages, configurations etc. """ +import importlib.metadata import os from tank_vendor import yaml -from . import constants -from . import LogManager - -from .util import yaml_cache -from .util import StorageRoots -from .util import ShotgunPath -from .util.shotgun import get_deferred_sg_connection - +from . import LogManager, constants from .errors import TankError +from .util import ShotgunPath, StorageRoots, yaml_cache +from .util.shotgun import get_deferred_sg_connection logger = LogManager.get_logger(__name__) @@ -439,7 +435,16 @@ def get_currently_running_api_version(): info_yml_path = os.path.abspath( os.path.join(os.path.dirname(__file__), "..", "..", "info.yml") ) - return _get_version_from_manifest(info_yml_path) + version = _get_version_from_manifest(info_yml_path) + if version is not None: + return version + # In a pip install the flat site-packages layout has no info.yml. + # Fall back to the installed distribution metadata; PEP 440 strips the + # leading 'v', so re-add it to match the info.yml convention. + try: + return "v" + importlib.metadata.version("sgtk") + except importlib.metadata.PackageNotFoundError: + return "unknown" def get_core_api_version(core_install_root): @@ -455,7 +460,19 @@ def get_core_api_version(core_install_root): """ # now try to get to the info.yml file to get the version number info_yml_path = os.path.join(core_install_root, "install", "core", "info.yml") - return _get_version_from_manifest(info_yml_path) + version = _get_version_from_manifest(info_yml_path) + if version is not None: + return version + # In a pip install the flat site-packages layout has no install/core/info.yml. + # If the requested core is the currently-running one, defer to + # get_currently_running_api_version which falls back to distribution metadata. + try: + current_core_root = get_path_to_current_core() + except TankError: + return "unknown" + if os.path.realpath(core_install_root) == os.path.realpath(current_core_root): + return get_currently_running_api_version() + return "unknown" def _get_version_from_manifest(info_yml_path): @@ -464,15 +481,14 @@ def _get_version_from_manifest(info_yml_path): Returns the version given a manifest. :param info_yml_path: path to manifest file. - :returns: Always a string, 'unknown' if data cannot be found + :returns: Version string, or None if data cannot be found. """ try: data = yaml_cache.g_yaml_cache.get(info_yml_path, deepcopy_data=False) or {} - data = str(data.get("version", "unknown")) + version = data.get("version") + return str(version) if version is not None else None except Exception: - data = "unknown" - - return data + return None def _get_core_descriptor_file(pipeline_config_path): diff --git a/python/tk-core/python/tank/platform/__init__.py b/python/tk-core/python/tank/platform/__init__.py index 801f4ba7..f772af42 100644 --- a/python/tk-core/python/tank/platform/__init__.py +++ b/python/tk-core/python/tank/platform/__init__.py @@ -9,28 +9,38 @@ # not expressly granted therein are reserved by Shotgun Software Inc. +from . import events + +# base classes to derive from +from .application import Application + # Engine management -from .engine import start_engine, current_engine, get_engine_path, find_app_settings +from .engine import ( + Engine, + current_engine, + find_app_settings, + get_engine_path, + start_engine, +) from .errors import ( - TankEngineInitError, - TankUnresolvedEnvironmentError, TankContextChangeNotSupportedError, + TankEngineInitError, TankMissingEngineError, TankMissingEnvironmentFile, + TankUnresolvedEnvironmentError, ) -from .software_launcher import create_engine_launcher - -# base classes to derive from -from .application import Application -from .engine import Engine -from .software_launcher import SoftwareLauncher, SoftwareVersion, LaunchInformation from .framework import Framework +from .software_launcher import ( + LaunchInformation, + SoftwareLauncher, + SoftwareVersion, + create_engine_launcher, +) from .util import ( change_context, + current_bundle, get_framework, + get_logger, import_framework, - current_bundle, restart, - get_logger, ) -from . import events diff --git a/python/tk-core/python/tank/platform/application.py b/python/tk-core/python/tank/platform/application.py index f97b5293..e37ba7a9 100644 --- a/python/tk-core/python/tank/platform/application.py +++ b/python/tk-core/python/tank/platform/application.py @@ -17,10 +17,9 @@ import sys from ..util.loader import load_plugin +from ..util.metrics import EventMetric from . import constants - from .bundle import TankBundle -from ..util.metrics import EventMetric class Application(TankBundle): diff --git a/python/tk-core/python/tank/platform/bundle.py b/python/tk-core/python/tank/platform/bundle.py index 3347cff5..3f84e690 100644 --- a/python/tk-core/python/tank/platform/bundle.py +++ b/python/tk-core/python/tank/platform/bundle.py @@ -827,7 +827,7 @@ def __resolve_hook_path(self, settings_name, hook_expression): # Entries are on the following form # # hook_publish_file: - # type: hook + # type: hook # description: Called when a file is published, e.g. copied from a work area to a publish area. # default_value: maya_publish_file # @@ -1074,7 +1074,7 @@ def _get_engine_name(self): # therefore get an error. try: engine_name = self.engine.name - except: + except Exception: engine_name = None return engine_name @@ -1133,7 +1133,7 @@ def _post_process_settings_r(tk, key, value, schema, bundle=None): items = schema.get("items", {}) # note - we assign the original values here because we processed_val = value - for (key, value_schema) in items.items(): + for key, value_schema in items.items(): processed_val[key] = _post_process_settings_r( tk=tk, key=key, value=value[key], schema=value_schema, bundle=bundle ) diff --git a/python/tk-core/python/tank/platform/engine.py b/python/tk-core/python/tank/platform/engine.py index 636fc33b..c68d3f54 100644 --- a/python/tk-core/python/tank/platform/engine.py +++ b/python/tk-core/python/tank/platform/engine.py @@ -12,44 +12,39 @@ Defines the base class for all Tank Engines. """ -import os -import sys +from __future__ import annotations # required to support python 3.9 + +import inspect import logging +import os import pprint +import sys +import threading import traceback -import inspect import weakref -import threading -from ..util.qt_importer import QtImporter -from ..util.loader import load_plugin -from .. import hook +from tank.flowam import host as flow_host # noqa: F401 (used in return annotation) +from tank.flowam import utils as flow_utils +from .. import hook from ..errors import TankError +from ..log import LogManager +from ..util import metrics_cache +from ..util import sgre as re +from ..util.loader import load_plugin +from ..util.metrics import EventMetric, MetricsDispatcher +from ..util.qt_importer import QtImporter +from . import application, constants, events, qt, qt5, qt6, validation +from .bundle import TankBundle +from .engine_logging import ToolkitEngineHandler, ToolkitEngineLegacyHandler from .errors import ( - TankEngineInitError, - TankUnresolvedEnvironmentError, TankContextChangeNotSupportedError, TankEngineEventError, + TankEngineInitError, TankMissingEngineError, + TankUnresolvedEnvironmentError, ) - -from ..util import sgre as re -from ..util.metrics import EventMetric -from ..util.metrics import MetricsDispatcher -from ..util import metrics_cache -from ..log import LogManager - -from . import application -from . import constants -from . import validation -from . import events -from . import qt -from . import qt5 -from . import qt6 -from .bundle import TankBundle from .framework import setup_frameworks -from .engine_logging import ToolkitEngineHandler, ToolkitEngineLegacyHandler # std core level logger core_logger = LogManager.get_logger(__name__) @@ -68,6 +63,11 @@ def __init__(self, tk, context, engine_instance_name, env): Engine instances are constructed by the toolkit launch process and various factory methods such as :meth:`start_engine`. + For Flow-enabled contexts (``context.flow_project_id`` set), this + also runs ``flow_utils.init_flow()`` to set up the Flow Integration + SDK session and provision pipeline schemas when the SG schema + config version does not match the bundled config. + :param tk: :class:`~sgtk.Sgtk` instance :param context: A context object to define the context on disk where the engine is operating :type context: :class:`~sgtk.Context` @@ -105,6 +105,10 @@ def __init__(self, tk, context, engine_instance_name, env): self._invoker = None self._async_invoker = None + # Flow host object used in Flow asset management integration + # Engines that support Flow integration will initialize this value to an instance of FlowHost + self._flow_host = None + # get the engine settings settings = self.__env.get_engine_settings(self.__engine_instance_name) @@ -162,6 +166,18 @@ def __init__(self, tk, context, engine_instance_name, env): self.log_debug("Appending to PYTHONPATH: %s" % python_path) sys.path.append(python_path) + # Do Flow sdk initialization if context is configured with Flow + if context.flow_project_id: + try: + flow_utils.init_flow( + tk.pipeline_configuration, + tk.shotgun, + context, + ) + except RuntimeError as exc: + self.log_error("Error occurred during Flow initialization!") + self.log_exception(exc) + # Note, 'init_engine()' is now deprecated and all derived initialisation should be # done in either 'pre_app_init()' or 'post_app_init()'. 'init_engine()' is left # in here to provide backwards compatibility with any legacy code. @@ -322,7 +338,7 @@ def __open_log_folder(self): if self.has_ui: # only import QT if we have a UI - from .qt import QtGui, QtCore + from .qt import QtCore, QtGui url = QtCore.QUrl.fromLocalFile(LogManager().log_folder) status = QtGui.QDesktopServices.openUrl(url) @@ -410,10 +426,10 @@ def __show_busy(self, title, details): if self.has_ui: # we cannot import QT until here as non-ui engines don't have QT defined. try: + from .qt import QtCore from .qt.busy_dialog import BusyDialog - from .qt import QtGui, QtCore - except: + except Exception: # QT import failed. This may be because someone has upgraded the core # to the latest but are still running a earlier version of the # Shotgun or Shell engine where the self.has_ui method is not @@ -684,6 +700,18 @@ def register_toggle_debug_command(self): """ return True + @property + def flow_host(self) -> flow_host.FlowHost | None: # noqa: F811 + """If the current context is Flow enabled, and the current + engine supports Flow integration, this value will be an instance of FlowHost. + The FlowHost class implements the required interface for the Flow asset management + integration to work within a dcc/engine. + + If the current context is not Flow enabled, or the current engine has not had + Flow support added, the value will be None. + """ + return self._flow_host + ########################################################################################## # init and destroy @@ -774,7 +802,7 @@ def change_context(self, new_context): # context change, it's that the target context isn't configured properly. # As such, we'll let any exceptions (mostly TankEngineInitError) bubble # up since it's a critical error case. - (new_env, engine_descriptor) = get_env_and_descriptor_for_engine( + new_env, engine_descriptor = get_env_and_descriptor_for_engine( engine_name=self.instance_name, tk=self.tank, context=new_context ) @@ -808,7 +836,6 @@ def change_context(self, new_context): # a context change. If one of them is not, then we remove it # from the persistent app pool, which will force it to be # rebuilt when apps are loaded later on. - non_compliant_app_paths = [] for install_path, app_instances in self.__application_pool.items(): for instance_name, app in app_instances.items(): self.log_debug( @@ -1198,7 +1225,7 @@ def _execute_in_main_thread(self, invoker_id, func, *args, **kwargs): self._invoker if invoker_id == self._SYNC_INVOKER else self._async_invoker ) if invoker: - from .qt import QtGui, QtCore + from .qt import QtCore, QtGui if ( QtGui.QApplication.instance() @@ -1247,7 +1274,7 @@ def get_matching_commands(self, command_selectors): """ # return a dictionary grouping all the commands by instance name commands_by_instance = {} - for (name, value) in self.commands.items(): + for name, value in self.commands.items(): app_instance = value["properties"].get("app") if app_instance is None: continue @@ -1684,7 +1711,8 @@ def _create_widget(self, widget_class, *args, **kwargs): self.logger.exception(exc) import traceback - from sgtk.platform.qt import QtGui, QtCore + + from sgtk.platform.qt import QtCore, QtGui # A very simple widget that ensures that the exception is visible and # selectable should the user need to copy/paste it into a support @@ -1984,7 +2012,7 @@ def _resolve_sg_stylesheet_tokens(self, style_sheet): :returns: Stylesheet string with replacements applied """ processed_style_sheet = style_sheet - for (token, value) in constants.SG_STYLESHEET_CONSTANTS.items(): + for token, value in constants.SG_STYLESHEET_CONSTANTS.items(): processed_style_sheet = processed_style_sheet.replace( "{{%s}}" % token, value ) @@ -2128,7 +2156,12 @@ def _define_qt_base(self): :returns: dict """ - base = {"qt_core": None, "qt_gui": None, "qt_web_engine_widgets": None, "dialog_base": None} + base = { + "qt_core": None, + "qt_gui": None, + "qt_web_engine_widgets": None, + "dialog_base": None, + } try: importer = QtImporter() base["qt_core"] = importer.QtCore @@ -2140,7 +2173,7 @@ def _define_qt_base(self): base["dialog_base"] = None base["wrapper"] = importer.binding base["shiboken"] = importer.shiboken - except: + except Exception: self.log_exception( "Default engine QT definition failed to find QT. " @@ -2441,7 +2474,7 @@ def __create_invokers(self): invoker = None async_invoker = None if self.has_ui: - from .qt import QtGui, QtCore + from .qt import QtCore, QtGui # Classes are defined locally since Qt might not be available. if QtGui and QtCore: @@ -2863,7 +2896,6 @@ def current_engine(): :returns: :class:`Engine` instance or None if no engine is running. """ - global g_current_engine return g_current_engine @@ -2892,7 +2924,7 @@ def get_engine_path(engine_name, tk, context): """ # get environment and engine location try: - (env, engine_descriptor) = get_env_and_descriptor_for_engine( + env, engine_descriptor = get_env_and_descriptor_for_engine( engine_name, tk, context ) except TankEngineInitError: @@ -3068,7 +3100,7 @@ def _start_engine(engine_name, tk, old_context, new_context): LogManager().initialize_base_file_handler(engine_name) # get environment and engine location - (env, engine_descriptor) = get_env_and_descriptor_for_engine( + env, engine_descriptor = get_env_and_descriptor_for_engine( engine_name, tk, new_context ) diff --git a/python/tk-core/python/tank/platform/environment.py b/python/tk-core/python/tank/platform/environment.py index 1a2f176c..faa46240 100644 --- a/python/tk-core/python/tank/platform/environment.py +++ b/python/tk-core/python/tank/platform/environment.py @@ -13,19 +13,18 @@ """ +import copy import os import sys -import copy from tank_vendor import yaml -from .bundle import resolve_default_value -from . import constants -from . import environment_includes -from ..errors import TankError, TankUnreadableFileError -from .errors import TankMissingEnvironmentFile -from ..util.yaml_cache import g_yaml_cache from .. import LogManager +from ..errors import TankError, TankUnreadableFileError +from ..util.yaml_cache import g_yaml_cache +from . import constants, environment_includes +from .bundle import resolve_default_value +from .errors import TankMissingEnvironmentFile logger = LogManager.get_logger(__name__) @@ -67,8 +66,7 @@ def __str__(self): return "Environment %s" % os.path.basename(self._env_path) def _refresh(self): - """Refreshes the environment data from disk - """ + """Refreshes the environment data from disk""" data = self.__load_environment_data() self._env_data = environment_includes.process_includes( @@ -233,7 +231,7 @@ def __extract_locations(self): constants.ENVIRONMENT_LOCATION_KEY ) - for (eng, app) in self.__app_settings: + for eng, app in self.__app_settings: descriptor_dict = self.__app_settings[(eng, app)].get( constants.ENVIRONMENT_LOCATION_KEY ) @@ -280,7 +278,7 @@ def name(self): without its extension """ file_name_with_ext = os.path.basename(self._env_path) - (file_name, ext) = os.path.splitext(file_name_with_ext) + file_name, ext = os.path.splitext(file_name_with_ext) return file_name @property @@ -323,7 +321,7 @@ def get_apps(self, engine): apps = [] engine_app_tuples = list(self.__app_settings.keys()) - for (engine_name, app_name) in engine_app_tuples: + for engine_name, app_name in engine_app_tuples: if engine_name == engine: apps.append(app_name) return apps @@ -597,7 +595,7 @@ def _find_location_for_app(self, engine_name, app_name, absolute_location=False) absolute_location, ) # first, find the location of the engine: - (engine_tokens, engine_yml_file) = self.find_location_for_engine(engine_name) + engine_tokens, engine_yml_file = self.find_location_for_engine(engine_name) # load the engine data: engine_yml_data = self.__load_data(engine_yml_file) @@ -814,7 +812,7 @@ class WritableEnvironment(InstalledEnvironment): content back to disk. """ - (NONE, INCLUDE_DEFAULTS, STRIP_DEFAULTS) = range(3) + NONE, INCLUDE_DEFAULTS, STRIP_DEFAULTS = range(3) """Format enumeration to use when dumping an environment. NONE: Don't modify the settings. @@ -834,7 +832,7 @@ def __init__(self, env_path, pipeline_config, context=None): super().__init__(env_path, pipeline_config, context) def _get_ruamel_yaml(self): - vendor_path = os.path.join(os.path.dirname(__file__), "../..", "tank_vendor") + vendor_path = os.path.join(os.path.dirname(__file__), "../..", "tank_vendor") if vendor_path not in sys.path: sys.path.append(vendor_path) @@ -1014,7 +1012,7 @@ def _update_location_data(self, data, new_location_data): # It is the difference between the following: # # common.engines.tk-maya.location: - # type: app_store + # type: app_store # name: tk-maya # version: v0.8.1 # @@ -1022,7 +1020,7 @@ def _update_location_data(self, data, new_location_data): # # common.apps.tk-multi-shotgunpanel: # location: - # type: app_store + # type: app_store # name: tk-multi-shotgunpanel # version: v1.4.3 # @@ -1052,7 +1050,7 @@ def update_engine_settings(self, engine_name, new_data, new_location): # in a concrete manner (ie: the actual dict and not an include # to another yml file). The absolute_location argument will allow # us to do that. - (tokens, yml_file) = self._find_location_for_engine( + tokens, yml_file = self._find_location_for_engine( engine_name, absolute_location=True ) @@ -1095,7 +1093,7 @@ def update_app_settings(self, engine_name, app_name, new_data, new_location): # in a concrete manner (ie: the actual dict and not an include # to another yml file). The absolute_location argument will allow # us to do that. - (tokens, yml_file) = self._find_location_for_app( + tokens, yml_file = self._find_location_for_app( engine_name, app_name, absolute_location=True ) @@ -1133,7 +1131,7 @@ def update_framework_settings(self, framework_name, new_data, new_location): # in a concrete manner (ie: the actual dict and not an include # to another yml file). The absolute_location argument will allow # us to do that. - (tokens, yml_file) = self._find_location_for_framework( + tokens, yml_file = self._find_location_for_framework( framework_name, absolute_location=True ) @@ -1405,7 +1403,7 @@ def dump(self, file, transform, include_debug_comments=True): for engine_name in self.get_engines(): # only process settings in this file - (tokens, engine_file) = self.find_location_for_engine(engine_name) + tokens, engine_file = self.find_location_for_engine(engine_name) if not engine_file == self._env_path: continue @@ -1438,7 +1436,7 @@ def dump(self, file, transform, include_debug_comments=True): for app_name in self.get_apps(engine_name): # only process settings in this file - (tokens, app_file) = self.find_location_for_app(engine_name, app_name) + tokens, app_file = self.find_location_for_app(engine_name, app_name) if not app_file == self._env_path: continue @@ -1471,7 +1469,7 @@ def dump(self, file, transform, include_debug_comments=True): for fw_name in self.get_frameworks(): # only process settings in this file - (tokens, fw_file) = self.find_location_for_framework(fw_name) + tokens, fw_file = self.find_location_for_framework(fw_name) if not fw_file == self._env_path: continue diff --git a/python/tk-core/python/tank/platform/environment_includes.py b/python/tk-core/python/tank/platform/environment_includes.py index 3f4ec8ff..bd47068b 100644 --- a/python/tk-core/python/tank/platform/environment_includes.py +++ b/python/tk-core/python/tank/platform/environment_includes.py @@ -27,21 +27,17 @@ """ - -import os -import sys import copy +import os from ..errors import TankError +from ..log import LogManager from ..template import TemplatePath from ..templatekey import StringKey -from ..log import LogManager - -from . import constants - from ..util import sgre as re -from ..util.yaml_cache import g_yaml_cache from ..util.includes import resolve_include +from ..util.yaml_cache import g_yaml_cache +from . import constants log = LogManager.get_logger(__name__) @@ -106,7 +102,7 @@ def _resolve_includes(file_name, data, context): try: f = context.as_template_fields(template) full_path = template.apply_fields(f) - except TankError as e: + except TankError: # if this path could not be resolved, that's ok! These paths are always optional. continue @@ -136,7 +132,7 @@ def _resolve_refs_r(lookup_dict, data): elif isinstance(data, dict): processed_val = {} - for (k, v) in data.items(): + for k, v in data.items(): processed_val[k] = _resolve_refs_r(lookup_dict, v) elif isinstance(data, str) and data.startswith("@"): @@ -350,9 +346,7 @@ def find_reference(file_name, context, token, absolute_location=False): # If the value of the token is an include, then we can # recurse up, directly referencing the include name as # the new token. - if isinstance(token_data, str) and token_data.startswith( - "@" - ): + if isinstance(token_data, str) and token_data.startswith("@"): include_token = token_data else: # In the case where the data isn't itself an include, diff --git a/python/tk-core/python/tank/platform/events/__init__.py b/python/tk-core/python/tank/platform/events/__init__.py index 4d80ad84..67615b8d 100644 --- a/python/tk-core/python/tank/platform/events/__init__.py +++ b/python/tk-core/python/tank/platform/events/__init__.py @@ -9,5 +9,5 @@ # not expressly granted therein are reserved by Shotgun Software Inc. from .event_engine import EngineEvent -from .event_file_open import FileOpenEvent from .event_file_close import FileCloseEvent +from .event_file_open import FileOpenEvent diff --git a/python/tk-core/python/tank/platform/framework.py b/python/tk-core/python/tank/platform/framework.py index bd81825c..cd17f76a 100644 --- a/python/tk-core/python/tank/platform/framework.py +++ b/python/tk-core/python/tank/platform/framework.py @@ -15,12 +15,10 @@ import os -from ..util.loader import load_plugin -from . import constants - from ..errors import TankError +from ..util.loader import load_plugin +from . import constants, validation from .bundle import TankBundle -from . import validation class Framework(TankBundle): diff --git a/python/tk-core/python/tank/platform/qt/busy_dialog.py b/python/tk-core/python/tank/platform/qt/busy_dialog.py index c9b0a612..a51e48a6 100644 --- a/python/tk-core/python/tank/platform/qt/busy_dialog.py +++ b/python/tk-core/python/tank/platform/qt/busy_dialog.py @@ -8,7 +8,7 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -from . import QtCore, QtGui +from . import QtGui from .ui_busy_dialog import Ui_BusyDialog diff --git a/python/tk-core/python/tank/platform/qt/config_item.py b/python/tk-core/python/tank/platform/qt/config_item.py index 44db298c..6068e519 100644 --- a/python/tk-core/python/tank/platform/qt/config_item.py +++ b/python/tk-core/python/tank/platform/qt/config_item.py @@ -8,14 +8,10 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -import os -import shutil -import sys - -from . import QtCore, QtGui -from .ui_item import Ui_Item from ..bundle import resolve_default_value from ..engine import current_engine +from . import QtGui +from .ui_item import Ui_Item class ConfigItem(QtGui.QWidget): @@ -47,7 +43,7 @@ def __init__(self, setting, params, value, bundle, parent=None): # special cases for some things: value_str = "" - if type(value) == str and value.startswith("hook:"): + if isinstance(value, str) and value.startswith("hook:"): # this is the generic hook override that any type can have value_str = "Value: %s" % value value_str += "

" diff --git a/python/tk-core/python/tank/platform/qt/resources_rc.py b/python/tk-core/python/tank/platform/qt/resources_rc.py index 51f7b1fa..85a139af 100644 --- a/python/tk-core/python/tank/platform/qt/resources_rc.py +++ b/python/tk-core/python/tank/platform/qt/resources_rc.py @@ -1959,10 +1959,17 @@ \x00\x00\x01\x8f05\xd2\x9e\ " + def qInitResources(): - QtCore.qRegisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) + QtCore.qRegisterResourceData( + 0x03, qt_resource_struct, qt_resource_name, qt_resource_data + ) + def qCleanupResources(): - QtCore.qUnregisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) + QtCore.qUnregisterResourceData( + 0x03, qt_resource_struct, qt_resource_name, qt_resource_data + ) + qInitResources() diff --git a/python/tk-core/python/tank/platform/qt/tankqdialog.py b/python/tk-core/python/tank/platform/qt/tankqdialog.py index da378e35..52d06905 100644 --- a/python/tk-core/python/tank/platform/qt/tankqdialog.py +++ b/python/tk-core/python/tank/platform/qt/tankqdialog.py @@ -13,18 +13,13 @@ """ -from . import QtCore, QtGui -from . import ui_tank_dialog -from . import TankDialogBase -from .config_item import ConfigItem -from .. import engine -from .. import application -from .. import constants -from ...errors import TankError - -import sys -import os import inspect +import os + +from ...errors import TankError +from .. import application, constants, engine +from . import QtCore, QtGui, TankDialogBase, ui_tank_dialog +from .config_item import ConfigItem class TankQDialog(TankDialogBase): @@ -70,7 +65,7 @@ def _stop_buggy_background_worker_qthreads(widget): # stop if we've previously checked this class: cls_type = checked_classes.get(cls, None) - if cls_type != None: + if cls_type is not None: break checked_classes[cls] = "" @@ -104,7 +99,7 @@ def _stop_buggy_background_worker_qthreads(widget): # assume that this is derived from an actual tk-multi-workfiles.SaveAsForm! cls_type = "SaveAsForm" - if cls_type != None: + if cls_type is not None: checked_classes[cls] = cls_type break @@ -331,7 +326,8 @@ def _format_context_property(p, show_type=False): tooltip += "System Information" tooltip += "
" tooltip += ( - "Flow Production Tracking Toolkit Version: %s
" % self._bundle.tank.version + "Flow Production Tracking Toolkit Version: %s
" + % self._bundle.tank.version ) tooltip += "Pipeline Config: %s
" % pc.get_name() tooltip += "Config Path: %s
" % pc.get_path() @@ -360,7 +356,7 @@ def _format_context_property(p, show_type=False): context_info += "You are currently running in the %s environment." % ( self._bundle.engine.environment["name"] ) - except: + except Exception: pass self.ui.app_work_area_info.setText(context_info) diff --git a/python/tk-core/python/tank/platform/qt/ui_busy_dialog.py b/python/tk-core/python/tank/platform/qt/ui_busy_dialog.py index d81bbfb3..a62690fb 100644 --- a/python/tk-core/python/tank/platform/qt/ui_busy_dialog.py +++ b/python/tk-core/python/tank/platform/qt/ui_busy_dialog.py @@ -9,69 +9,76 @@ ################################################################################ from . import QtCore + for name, cls in QtCore.__dict__.items(): - if isinstance(cls, type): globals()[name] = cls + if isinstance(cls, type): + globals()[name] = cls from . import QtGui + for name, cls in QtGui.__dict__.items(): - if isinstance(cls, type): globals()[name] = cls + if isinstance(cls, type): + globals()[name] = cls + +from . import resources_rc -from . import resources_rc class Ui_BusyDialog(object): def setupUi(self, BusyDialog): if not BusyDialog.objectName(): - BusyDialog.setObjectName(u"BusyDialog") + BusyDialog.setObjectName("BusyDialog") BusyDialog.resize(500, 110) - BusyDialog.setStyleSheet(u"/* Style for the window itself */\n" -"#frame {\n" -"border-color: #30A7E3;\n" -"border-style: solid;\n" -"border-width: 2px;\n" -"}\n" -"\n" -"/* Style for the header text */\n" -"#title {\n" -"color: #30A7E3;\n" -"margin-top: 15px;\n" -"margin-bottom: 0px;\n" -"margin-left: 1px;\n" -"font-size: 16px;\n" -"font-weight: bold;\n" -"}\n" -"\n" -"/* Style for the details text */\n" -"#details {\n" -"margin-top: 1px;\n" -"margin-left: 3px;\n" -"margin-bottom: 0px;\n" -"font-size: 11px;\n" -"}\n" -"") + BusyDialog.setStyleSheet( + "/* Style for the window itself */\n" + "#frame {\n" + "border-color: #30A7E3;\n" + "border-style: solid;\n" + "border-width: 2px;\n" + "}\n" + "\n" + "/* Style for the header text */\n" + "#title {\n" + "color: #30A7E3;\n" + "margin-top: 15px;\n" + "margin-bottom: 0px;\n" + "margin-left: 1px;\n" + "font-size: 16px;\n" + "font-weight: bold;\n" + "}\n" + "\n" + "/* Style for the details text */\n" + "#details {\n" + "margin-top: 1px;\n" + "margin-left: 3px;\n" + "margin-bottom: 0px;\n" + "font-size: 11px;\n" + "}\n" + "" + ) self.horizontalLayout_2 = QHBoxLayout(BusyDialog) self.horizontalLayout_2.setSpacing(2) self.horizontalLayout_2.setContentsMargins(2, 2, 2, 2) - self.horizontalLayout_2.setObjectName(u"horizontalLayout_2") + self.horizontalLayout_2.setObjectName("horizontalLayout_2") self.frame = QFrame(BusyDialog) - self.frame.setObjectName(u"frame") + self.frame.setObjectName("frame") self.frame.setFrameShape(QFrame.StyledPanel) self.frame.setFrameShadow(QFrame.Raised) self.horizontalLayout = QHBoxLayout(self.frame) self.horizontalLayout.setSpacing(5) self.horizontalLayout.setContentsMargins(5, 5, 5, 5) - self.horizontalLayout.setObjectName(u"horizontalLayout") + self.horizontalLayout.setObjectName("horizontalLayout") self.label = QLabel(self.frame) - self.label.setObjectName(u"label") - self.label.setPixmap(QPixmap(u":/Tank.Platform.Qt/sg_logo_80px.png")) + self.label.setObjectName("label") + self.label.setPixmap(QPixmap(":/Tank.Platform.Qt/sg_logo_80px.png")) self.horizontalLayout.addWidget(self.label) self.verticalLayout = QVBoxLayout() self.verticalLayout.setSpacing(0) - self.verticalLayout.setObjectName(u"verticalLayout") + self.verticalLayout.setObjectName("verticalLayout") self.title = QLabel(self.frame) - self.title.setObjectName(u"title") + self.title.setObjectName("title") sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) @@ -81,13 +88,13 @@ def setupUi(self, BusyDialog): self.verticalLayout.addWidget(self.title) self.details = QLabel(self.frame) - self.details.setObjectName(u"details") + self.details.setObjectName("details") sizePolicy1 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) sizePolicy1.setHorizontalStretch(0) sizePolicy1.setVerticalStretch(0) sizePolicy1.setHeightForWidth(self.details.sizePolicy().hasHeightForWidth()) self.details.setSizePolicy(sizePolicy1) - self.details.setAlignment(Qt.AlignLeading|Qt.AlignLeft|Qt.AlignTop) + self.details.setAlignment(Qt.AlignLeading | Qt.AlignLeft | Qt.AlignTop) self.details.setWordWrap(True) self.verticalLayout.addWidget(self.details) @@ -99,11 +106,21 @@ def setupUi(self, BusyDialog): self.retranslateUi(BusyDialog) QMetaObject.connectSlotsByName(BusyDialog) + # setupUi def retranslateUi(self, BusyDialog): - BusyDialog.setWindowTitle(QCoreApplication.translate("BusyDialog", u"Dialog", None)) + BusyDialog.setWindowTitle( + QCoreApplication.translate("BusyDialog", "Dialog", None) + ) self.label.setText("") - self.title.setText(QCoreApplication.translate("BusyDialog", u"Doing something, hang on!", None)) - self.details.setText(QCoreApplication.translate("BusyDialog", u"Lots of interesting details about what is going on", None)) + self.title.setText( + QCoreApplication.translate("BusyDialog", "Doing something, hang on!", None) + ) + self.details.setText( + QCoreApplication.translate( + "BusyDialog", "Lots of interesting details about what is going on", None + ) + ) + # retranslateUi diff --git a/python/tk-core/python/tank/platform/qt/ui_item.py b/python/tk-core/python/tank/platform/qt/ui_item.py index 0f1bc565..1b6f8aa9 100644 --- a/python/tk-core/python/tank/platform/qt/ui_item.py +++ b/python/tk-core/python/tank/platform/qt/ui_item.py @@ -9,56 +9,61 @@ ################################################################################ from . import QtCore + for name, cls in QtCore.__dict__.items(): - if isinstance(cls, type): globals()[name] = cls + if isinstance(cls, type): + globals()[name] = cls from . import QtGui + for name, cls in QtGui.__dict__.items(): - if isinstance(cls, type): globals()[name] = cls + if isinstance(cls, type): + globals()[name] = cls + +from . import resources_rc -from . import resources_rc class Ui_Item(object): def setupUi(self, Item): if not Item.objectName(): - Item.setObjectName(u"Item") + Item.setObjectName("Item") Item.resize(335, 110) sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Minimum) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(Item.sizePolicy().hasHeightForWidth()) Item.setSizePolicy(sizePolicy) - Item.setStyleSheet(u"QLabel{\n" -" font-size: 11px;\n" -" margin-bottom: 3px\n" -"}\n" -"") + Item.setStyleSheet( + "QLabel{\n" " font-size: 11px;\n" " margin-bottom: 3px\n" "}\n" "" + ) self.verticalLayout = QVBoxLayout(Item) self.verticalLayout.setContentsMargins(0, 0, 0, 0) - self.verticalLayout.setObjectName(u"verticalLayout") + self.verticalLayout.setObjectName("verticalLayout") self.verticalLayout_2 = QVBoxLayout() self.verticalLayout_2.setSpacing(0) - self.verticalLayout_2.setObjectName(u"verticalLayout_2") + self.verticalLayout_2.setObjectName("verticalLayout_2") self.name = QLabel(Item) - self.name.setObjectName(u"name") + self.name.setObjectName("name") sizePolicy1 = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum) sizePolicy1.setHorizontalStretch(0) sizePolicy1.setVerticalStretch(0) sizePolicy1.setHeightForWidth(self.name.sizePolicy().hasHeightForWidth()) self.name.setSizePolicy(sizePolicy1) - self.name.setStyleSheet(u"font-size: 13px;") - self.name.setAlignment(Qt.AlignLeading|Qt.AlignLeft|Qt.AlignVCenter) + self.name.setStyleSheet("font-size: 13px;") + self.name.setAlignment(Qt.AlignLeading | Qt.AlignLeft | Qt.AlignVCenter) self.name.setWordWrap(True) self.verticalLayout_2.addWidget(self.name) self.line = QFrame(Item) - self.line.setObjectName(u"line") - self.line.setStyleSheet(u"border: none;\n" -"border-bottom-color: rgba(150,150,150,100);\n" -"border-bottom-width: 1px;\n" -"border-bottom-style: solid;") + self.line.setObjectName("line") + self.line.setStyleSheet( + "border: none;\n" + "border-bottom-color: rgba(150,150,150,100);\n" + "border-bottom-width: 1px;\n" + "border-bottom-style: solid;" + ) self.line.setFrameShape(QFrame.HLine) self.line.setFrameShadow(QFrame.Sunken) @@ -67,36 +72,44 @@ def setupUi(self, Item): self.verticalLayout.addLayout(self.verticalLayout_2) self.value = QLabel(Item) - self.value.setObjectName(u"value") + self.value.setObjectName("value") sizePolicy1.setHeightForWidth(self.value.sizePolicy().hasHeightForWidth()) self.value.setSizePolicy(sizePolicy1) - self.value.setAlignment(Qt.AlignLeading|Qt.AlignLeft|Qt.AlignVCenter) + self.value.setAlignment(Qt.AlignLeading | Qt.AlignLeft | Qt.AlignVCenter) self.value.setWordWrap(True) - self.value.setTextInteractionFlags(Qt.LinksAccessibleByMouse|Qt.TextSelectableByMouse) + self.value.setTextInteractionFlags( + Qt.LinksAccessibleByMouse | Qt.TextSelectableByMouse + ) self.verticalLayout.addWidget(self.value) self.type = QLabel(Item) - self.type.setObjectName(u"type") + self.type.setObjectName("type") sizePolicy1.setHeightForWidth(self.type.sizePolicy().hasHeightForWidth()) self.type.setSizePolicy(sizePolicy1) - self.type.setAlignment(Qt.AlignLeading|Qt.AlignLeft|Qt.AlignVCenter) + self.type.setAlignment(Qt.AlignLeading | Qt.AlignLeft | Qt.AlignVCenter) self.type.setWordWrap(True) - self.type.setTextInteractionFlags(Qt.LinksAccessibleByMouse|Qt.TextSelectableByMouse) + self.type.setTextInteractionFlags( + Qt.LinksAccessibleByMouse | Qt.TextSelectableByMouse + ) self.verticalLayout.addWidget(self.type) self.description = QLabel(Item) - self.description.setObjectName(u"description") + self.description.setObjectName("description") self.description.setMaximumSize(QSize(350, 16777215)) self.description.setTextFormat(Qt.RichText) - self.description.setAlignment(Qt.AlignLeading|Qt.AlignLeft|Qt.AlignVCenter) + self.description.setAlignment(Qt.AlignLeading | Qt.AlignLeft | Qt.AlignVCenter) self.description.setWordWrap(True) - self.description.setTextInteractionFlags(Qt.LinksAccessibleByMouse|Qt.TextSelectableByMouse) + self.description.setTextInteractionFlags( + Qt.LinksAccessibleByMouse | Qt.TextSelectableByMouse + ) self.verticalLayout.addWidget(self.description) - self.verticalSpacer = QSpacerItem(20, 0, QSizePolicy.Minimum, QSizePolicy.Expanding) + self.verticalSpacer = QSpacerItem( + 20, 0, QSizePolicy.Minimum, QSizePolicy.Expanding + ) self.verticalLayout.addItem(self.verticalSpacer) @@ -105,16 +118,24 @@ def setupUi(self, Item): self.retranslateUi(Item) QMetaObject.connectSlotsByName(Item) + # setupUi def retranslateUi(self, Item): - Item.setWindowTitle(QCoreApplication.translate("Item", u"Form", None)) - self.name.setText(QCoreApplication.translate("Item", u"Settings Name", None)) - self.value.setText(QCoreApplication.translate("Item", u"Value: foo bar", None)) - self.type.setText(QCoreApplication.translate("Item", u"Type: bool", None)) - self.description.setText(QCoreApplication.translate("Item", u"\n" -"\n" -"

description

", None)) + Item.setWindowTitle(QCoreApplication.translate("Item", "Form", None)) + self.name.setText(QCoreApplication.translate("Item", "Settings Name", None)) + self.value.setText(QCoreApplication.translate("Item", "Value: foo bar", None)) + self.type.setText(QCoreApplication.translate("Item", "Type: bool", None)) + self.description.setText( + QCoreApplication.translate( + "Item", + '\n' + '\n" + '

description

', + None, + ) + ) + # retranslateUi diff --git a/python/tk-core/python/tank/platform/qt/ui_tank_dialog.py b/python/tk-core/python/tank/platform/qt/ui_tank_dialog.py index 344b2eb7..1f81444b 100644 --- a/python/tk-core/python/tank/platform/qt/ui_tank_dialog.py +++ b/python/tk-core/python/tank/platform/qt/ui_tank_dialog.py @@ -9,138 +9,153 @@ ################################################################################ from . import QtCore + for name, cls in QtCore.__dict__.items(): - if isinstance(cls, type): globals()[name] = cls + if isinstance(cls, type): + globals()[name] = cls from . import QtGui + for name, cls in QtGui.__dict__.items(): - if isinstance(cls, type): globals()[name] = cls + if isinstance(cls, type): + globals()[name] = cls + +from . import resources_rc -from . import resources_rc class Ui_TankDialog(object): def setupUi(self, TankDialog): if not TankDialog.objectName(): - TankDialog.setObjectName(u"TankDialog") + TankDialog.setObjectName("TankDialog") TankDialog.resize(879, 551) - TankDialog.setStyleSheet(u"") + TankDialog.setStyleSheet("") self.verticalLayout_3 = QVBoxLayout(TankDialog) self.verticalLayout_3.setSpacing(0) - self.verticalLayout_3.setObjectName(u"verticalLayout_3") + self.verticalLayout_3.setObjectName("verticalLayout_3") self.verticalLayout_3.setContentsMargins(0, 0, 0, 0) self.stackedWidget = QStackedWidget(TankDialog) - self.stackedWidget.setObjectName(u"stackedWidget") + self.stackedWidget.setObjectName("stackedWidget") self.page_1 = QWidget() - self.page_1.setObjectName(u"page_1") - self.page_1.setStyleSheet(u"QWidget#page_1 {\n" -"margin: 0px;\n" -"}") + self.page_1.setObjectName("page_1") + self.page_1.setStyleSheet("QWidget#page_1 {\n" "margin: 0px;\n" "}") self.verticalLayout = QVBoxLayout(self.page_1) self.verticalLayout.setSpacing(0) - self.verticalLayout.setObjectName(u"verticalLayout") + self.verticalLayout.setObjectName("verticalLayout") self.verticalLayout.setContentsMargins(0, 0, 0, 0) self.top_group = QGroupBox(self.page_1) - self.top_group.setObjectName(u"top_group") + self.top_group.setObjectName("top_group") self.top_group.setMinimumSize(QSize(0, 45)) self.top_group.setMaximumSize(QSize(16777215, 45)) - self.top_group.setStyleSheet(u"#top_group {\n" -"background-color: #2D2D2D;\n" -"border: none;\n" -"border-bottom:1px solid #202020;\n" -"}\n" -"") + self.top_group.setStyleSheet( + "#top_group {\n" + "background-color: #2D2D2D;\n" + "border: none;\n" + "border-bottom:1px solid #202020;\n" + "}\n" + "" + ) self.top_group.setFlat(False) self.horizontalLayout = QHBoxLayout(self.top_group) self.horizontalLayout.setSpacing(0) - self.horizontalLayout.setObjectName(u"horizontalLayout") + self.horizontalLayout.setObjectName("horizontalLayout") self.horizontalLayout.setContentsMargins(4, 0, 1, 1) self.tank_logo = QLabel(self.top_group) - self.tank_logo.setObjectName(u"tank_logo") + self.tank_logo.setObjectName("tank_logo") sizePolicy = QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.tank_logo.sizePolicy().hasHeightForWidth()) self.tank_logo.setSizePolicy(sizePolicy) - self.tank_logo.setPixmap(QPixmap(u":/Tank.Platform.Qt/tank_logo.png")) + self.tank_logo.setPixmap(QPixmap(":/Tank.Platform.Qt/tank_logo.png")) self.horizontalLayout.addWidget(self.tank_logo) self.label = QLabel(self.top_group) - self.label.setObjectName(u"label") - self.label.setStyleSheet(u"/* want this stylesheet to apply to the label but not the tooltip */\n" -"QLabel{\n" -" color: white;\n" -" font-size: 20px;\n" -" margin-left: 5px;\n" -" font-family: \"Open Sans\";\n" -" font-style: \"Regular\";\n" -"}") + self.label.setObjectName("label") + self.label.setStyleSheet( + "/* want this stylesheet to apply to the label but not the tooltip */\n" + "QLabel{\n" + " color: white;\n" + " font-size: 20px;\n" + " margin-left: 5px;\n" + ' font-family: "Open Sans";\n' + ' font-style: "Regular";\n' + "}" + ) self.horizontalLayout.addWidget(self.label) self.lbl_context = QLabel(self.top_group) - self.lbl_context.setObjectName(u"lbl_context") - self.lbl_context.setStyleSheet(u"/* want this stylesheet to apply to the label but not the tooltip */\n" -"QLabel {\n" -" color: rgba(250,250,250,180);\n" -" font-size: 11px;\n" -" margin-right: 8px;\n" -" font-family: \"Open Sans\";\n" -" font-style: \"Regular\";\n" -"}\n" -"\n" -"\n" -"") - self.lbl_context.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter) + self.lbl_context.setObjectName("lbl_context") + self.lbl_context.setStyleSheet( + "/* want this stylesheet to apply to the label but not the tooltip */\n" + "QLabel {\n" + " color: rgba(250,250,250,180);\n" + " font-size: 11px;\n" + " margin-right: 8px;\n" + ' font-family: "Open Sans";\n' + ' font-style: "Regular";\n' + "}\n" + "\n" + "\n" + "" + ) + self.lbl_context.setAlignment( + Qt.AlignRight | Qt.AlignTrailing | Qt.AlignVCenter + ) self.horizontalLayout.addWidget(self.lbl_context) self.details_show = QToolButton(self.top_group) - self.details_show.setObjectName(u"details_show") + self.details_show.setObjectName("details_show") self.details_show.setMinimumSize(QSize(34, 34)) self.details_show.setFocusPolicy(Qt.ClickFocus) - self.details_show.setStyleSheet(u"QToolButton{\n" -"width: 12px;\n" -"height: 20px;\n" -"background-image: url(:/Tank.Platform.Qt/arrow.png);\n" -"border: none;\n" -"background-color: none;\n" -"}\n" -"\n" -"QToolButton:hover{\n" -"background-image: url(:/Tank.Platform.Qt/arrow_hover.png);\n" -"}\n" -"\n" -"QToolButton:pressed{\n" -"background-image: url(:/Tank.Platform.Qt/arrow_pressed.png);\n" -"}\n" -"") + self.details_show.setStyleSheet( + "QToolButton{\n" + "width: 12px;\n" + "height: 20px;\n" + "background-image: url(:/Tank.Platform.Qt/arrow.png);\n" + "border: none;\n" + "background-color: none;\n" + "}\n" + "\n" + "QToolButton:hover{\n" + "background-image: url(:/Tank.Platform.Qt/arrow_hover.png);\n" + "}\n" + "\n" + "QToolButton:pressed{\n" + "background-image: url(:/Tank.Platform.Qt/arrow_pressed.png);\n" + "}\n" + "" + ) self.details_show.setAutoRaise(True) self.horizontalLayout.addWidget(self.details_show) self.details_hide = QToolButton(self.top_group) - self.details_hide.setObjectName(u"details_hide") + self.details_hide.setObjectName("details_hide") self.details_hide.setMinimumSize(QSize(34, 34)) self.details_hide.setFocusPolicy(Qt.ClickFocus) self.details_hide.setVisible(False) - self.details_hide.setStyleSheet(u"QToolButton{\n" -" width: 12px;\n" -" height: 20px;\n" -" background-image: url(:/Tank.Platform.Qt/arrow_flipped.png);\n" -" border: none;\n" -" background-color: none;\n" -" }\n" -"\n" -" QToolButton:hover{\n" -" background-image: url(:/Tank.Platform.Qt/arrow_flipped_hover.png);\n" -" }\n" -"\n" -" QToolButton:pressed{\n" -" background-image: url(:/Tank.Platform.Qt/arrow_flipped_pressed.png);\n" -" }\n" -" ") + self.details_hide.setStyleSheet( + "QToolButton{\n" + " width: 12px;\n" + " height: 20px;\n" + " background-image: url(:/Tank.Platform.Qt/arrow_flipped.png);\n" + " border: none;\n" + " background-color: none;\n" + " }\n" + "\n" + " QToolButton:hover{\n" + " background-image: url(:/Tank.Platform.Qt/arrow_flipped_hover.png);\n" + " }\n" + "\n" + " QToolButton:pressed{\n" + " background-image: url(:/Tank.Platform.Qt/arrow_flipped_pressed.png);\n" + " }\n" + " " + ) self.details_hide.setAutoRaise(True) self.horizontalLayout.addWidget(self.details_hide) @@ -149,79 +164,85 @@ def setupUi(self, TankDialog): self.target = QVBoxLayout() self.target.setSpacing(4) - self.target.setObjectName(u"target") + self.target.setObjectName("target") self.verticalLayout.addLayout(self.target) self.stackedWidget.addWidget(self.page_1) self.page_2 = QWidget() - self.page_2.setObjectName(u"page_2") - self.page_2.setStyleSheet(u"QWidget {\n" -" font-family: \"Open Sans\";\n" -" font-style: \"Regular\";\n" -"}") + self.page_2.setObjectName("page_2") + self.page_2.setStyleSheet( + "QWidget {\n" + ' font-family: "Open Sans";\n' + ' font-style: "Regular";\n' + "}" + ) self.verticalLayout_2 = QVBoxLayout(self.page_2) - self.verticalLayout_2.setObjectName(u"verticalLayout_2") + self.verticalLayout_2.setObjectName("verticalLayout_2") self.verticalLayout_2.setContentsMargins(1, 1, 1, 1) self.page_2_group = QGroupBox(self.page_2) - self.page_2_group.setObjectName(u"page_2_group") + self.page_2_group.setObjectName("page_2_group") self.page_2_group.setMinimumSize(QSize(0, 100)) - self.page_2_group.setStyleSheet(u"QGroupBox {\n" -"margin: 0px;\n" -"}") + self.page_2_group.setStyleSheet("QGroupBox {\n" "margin: 0px;\n" "}") self.horizontalLayout_2 = QHBoxLayout(self.page_2_group) self.horizontalLayout_2.setSpacing(0) - self.horizontalLayout_2.setObjectName(u"horizontalLayout_2") + self.horizontalLayout_2.setObjectName("horizontalLayout_2") self.horizontalLayout_2.setContentsMargins(0, 0, 0, 0) - self.horizontalSpacer = QSpacerItem(145, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) + self.horizontalSpacer = QSpacerItem( + 145, 20, QSizePolicy.Expanding, QSizePolicy.Minimum + ) self.horizontalLayout_2.addItem(self.horizontalSpacer) self.label_3 = QLabel(self.page_2_group) - self.label_3.setObjectName(u"label_3") + self.label_3.setObjectName("label_3") self.label_3.setMinimumSize(QSize(40, 0)) self.label_3.setMaximumSize(QSize(40, 16777215)) self.horizontalLayout_2.addWidget(self.label_3) self.gradient = QGroupBox(self.page_2_group) - self.gradient.setObjectName(u"gradient") + self.gradient.setObjectName("gradient") self.gradient.setMinimumSize(QSize(11, 0)) self.gradient.setMaximumSize(QSize(11, 16777215)) - self.gradient.setStyleSheet(u"#gradient {\n" -"background-image: url(:/Tank.Platform.Qt/gradient.png);\n" -"border: none;\n" -"}") + self.gradient.setStyleSheet( + "#gradient {\n" + "background-image: url(:/Tank.Platform.Qt/gradient.png);\n" + "border: none;\n" + "}" + ) self.horizontalLayout_2.addWidget(self.gradient) self.scrollArea = QScrollArea(self.page_2_group) - self.scrollArea.setObjectName(u"scrollArea") + self.scrollArea.setObjectName("scrollArea") self.scrollArea.setMinimumSize(QSize(400, 0)) self.scrollArea.setMaximumSize(QSize(400, 16777215)) - self.scrollArea.setStyleSheet(u"/*\n" -"All labels inside this scroll area should be 12px font.\n" -"This is to avoid the UI looking different in different app like\n" -"maya and nuke which all use slightly different style sheets.\n" -" */\n" -"QLabel{\n" -" font-size: 11px;\n" -" margin-bottom: 8px\n" -"}\n" -"") + self.scrollArea.setStyleSheet( + "/*\n" + "All labels inside this scroll area should be 12px font.\n" + "This is to avoid the UI looking different in different app like\n" + "maya and nuke which all use slightly different style sheets.\n" + " */\n" + "QLabel{\n" + " font-size: 11px;\n" + " margin-bottom: 8px\n" + "}\n" + "" + ) self.scrollArea.setWidgetResizable(True) self.scrollAreaWidgetContents = QWidget() - self.scrollAreaWidgetContents.setObjectName(u"scrollAreaWidgetContents") + self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents") self.scrollAreaWidgetContents.setGeometry(QRect(0, 0, 398, 550)) self.verticalLayout_4 = QVBoxLayout(self.scrollAreaWidgetContents) - self.verticalLayout_4.setObjectName(u"verticalLayout_4") + self.verticalLayout_4.setObjectName("verticalLayout_4") self.horizontalLayout_4 = QHBoxLayout() - self.horizontalLayout_4.setObjectName(u"horizontalLayout_4") + self.horizontalLayout_4.setObjectName("horizontalLayout_4") self.app_icon = QLabel(self.scrollAreaWidgetContents) - self.app_icon.setObjectName(u"app_icon") + self.app_icon.setObjectName("app_icon") self.app_icon.setMinimumSize(QSize(64, 64)) self.app_icon.setMaximumSize(QSize(64, 64)) - self.app_icon.setPixmap(QPixmap(u":/Tank.Platform.Qt/default_app_icon_256.png")) + self.app_icon.setPixmap(QPixmap(":/Tank.Platform.Qt/default_app_icon_256.png")) self.app_icon.setScaledContents(True) self.app_icon.setAlignment(Qt.AlignCenter) @@ -229,12 +250,11 @@ def setupUi(self, TankDialog): self.verticalLayout_8 = QVBoxLayout() self.verticalLayout_8.setSpacing(1) - self.verticalLayout_8.setObjectName(u"verticalLayout_8") + self.verticalLayout_8.setObjectName("verticalLayout_8") self.app_name = QLabel(self.scrollAreaWidgetContents) - self.app_name.setObjectName(u"app_name") - self.app_name.setStyleSheet(u"font-size: 16px;\n" -"") - self.app_name.setAlignment(Qt.AlignLeading|Qt.AlignLeft|Qt.AlignVCenter) + self.app_name.setObjectName("app_name") + self.app_name.setStyleSheet("font-size: 16px;\n" "") + self.app_name.setAlignment(Qt.AlignLeading | Qt.AlignLeft | Qt.AlignVCenter) self.verticalLayout_8.addWidget(self.app_name) @@ -243,61 +263,66 @@ def setupUi(self, TankDialog): self.verticalLayout_4.addLayout(self.horizontalLayout_4) self.app_description = QLabel(self.scrollAreaWidgetContents) - self.app_description.setObjectName(u"app_description") + self.app_description.setObjectName("app_description") self.app_description.setMaximumSize(QSize(350, 16777215)) self.app_description.setWordWrap(True) self.verticalLayout_4.addWidget(self.app_description) self.app_tech_details = QLabel(self.scrollAreaWidgetContents) - self.app_tech_details.setObjectName(u"app_tech_details") + self.app_tech_details.setObjectName("app_tech_details") sizePolicy1 = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) sizePolicy1.setHorizontalStretch(0) sizePolicy1.setVerticalStretch(0) - sizePolicy1.setHeightForWidth(self.app_tech_details.sizePolicy().hasHeightForWidth()) + sizePolicy1.setHeightForWidth( + self.app_tech_details.sizePolicy().hasHeightForWidth() + ) self.app_tech_details.setSizePolicy(sizePolicy1) self.app_tech_details.setMinimumSize(QSize(0, 22)) self.app_tech_details.setMaximumSize(QSize(16777215, 22)) - self.app_tech_details.setAlignment(Qt.AlignLeading|Qt.AlignLeft|Qt.AlignVCenter) + self.app_tech_details.setAlignment( + Qt.AlignLeading | Qt.AlignLeft | Qt.AlignVCenter + ) self.app_tech_details.setWordWrap(True) self.verticalLayout_4.addWidget(self.app_tech_details) self.horizontalLayout_9 = QHBoxLayout() self.horizontalLayout_9.setSpacing(2) - self.horizontalLayout_9.setObjectName(u"horizontalLayout_9") + self.horizontalLayout_9.setObjectName("horizontalLayout_9") self.btn_documentation = QToolButton(self.scrollAreaWidgetContents) - self.btn_documentation.setObjectName(u"btn_documentation") + self.btn_documentation.setObjectName("btn_documentation") self.horizontalLayout_9.addWidget(self.btn_documentation) self.btn_support = QToolButton(self.scrollAreaWidgetContents) - self.btn_support.setObjectName(u"btn_support") + self.btn_support.setObjectName("btn_support") self.horizontalLayout_9.addWidget(self.btn_support) - self.horizontalSpacer_5 = QSpacerItem(0, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) + self.horizontalSpacer_5 = QSpacerItem( + 0, 20, QSizePolicy.Expanding, QSizePolicy.Minimum + ) self.horizontalLayout_9.addItem(self.horizontalSpacer_5) self.verticalLayout_4.addLayout(self.horizontalLayout_9) self.label_5 = QLabel(self.scrollAreaWidgetContents) - self.label_5.setObjectName(u"label_5") - self.label_5.setStyleSheet(u"font-size: 16px;\n" -"margin-top: 30px;") + self.label_5.setObjectName("label_5") + self.label_5.setStyleSheet("font-size: 16px;\n" "margin-top: 30px;") self.verticalLayout_4.addWidget(self.label_5) self.line = QFrame(self.scrollAreaWidgetContents) - self.line.setObjectName(u"line") + self.line.setObjectName("line") self.line.setFrameShape(QFrame.HLine) self.line.setFrameShadow(QFrame.Sunken) self.verticalLayout_4.addWidget(self.line) self.app_work_area_info = QLabel(self.scrollAreaWidgetContents) - self.app_work_area_info.setObjectName(u"app_work_area_info") + self.app_work_area_info.setObjectName("app_work_area_info") self.app_work_area_info.setMaximumSize(QSize(350, 16777215)) self.app_work_area_info.setWordWrap(True) @@ -305,51 +330,52 @@ def setupUi(self, TankDialog): self.horizontalLayout_10 = QHBoxLayout() self.horizontalLayout_10.setSpacing(2) - self.horizontalLayout_10.setObjectName(u"horizontalLayout_10") + self.horizontalLayout_10.setObjectName("horizontalLayout_10") self.btn_file_system = QToolButton(self.scrollAreaWidgetContents) - self.btn_file_system.setObjectName(u"btn_file_system") + self.btn_file_system.setObjectName("btn_file_system") self.horizontalLayout_10.addWidget(self.btn_file_system) self.btn_shotgun = QToolButton(self.scrollAreaWidgetContents) - self.btn_shotgun.setObjectName(u"btn_shotgun") + self.btn_shotgun.setObjectName("btn_shotgun") self.horizontalLayout_10.addWidget(self.btn_shotgun) - self.horizontalSpacer_6 = QSpacerItem(0, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) + self.horizontalSpacer_6 = QSpacerItem( + 0, 20, QSizePolicy.Expanding, QSizePolicy.Minimum + ) self.horizontalLayout_10.addItem(self.horizontalSpacer_6) self.verticalLayout_4.addLayout(self.horizontalLayout_10) self.app_work_area_info_2 = QLabel(self.scrollAreaWidgetContents) - self.app_work_area_info_2.setObjectName(u"app_work_area_info_2") + self.app_work_area_info_2.setObjectName("app_work_area_info_2") self.app_work_area_info_2.setMaximumSize(QSize(350, 16777215)) self.app_work_area_info_2.setWordWrap(True) self.verticalLayout_4.addWidget(self.app_work_area_info_2) self.btn_reload = QToolButton(self.scrollAreaWidgetContents) - self.btn_reload.setObjectName(u"btn_reload") + self.btn_reload.setObjectName("btn_reload") self.verticalLayout_4.addWidget(self.btn_reload) self.config_header = QLabel(self.scrollAreaWidgetContents) - self.config_header.setObjectName(u"config_header") - self.config_header.setStyleSheet(u"font-size: 16px;\n" -"margin-top: 30px;") + self.config_header.setObjectName("config_header") + self.config_header.setStyleSheet("font-size: 16px;\n" "margin-top: 30px;") self.verticalLayout_4.addWidget(self.config_header) self.config_line = QFrame(self.scrollAreaWidgetContents) - self.config_line.setObjectName(u"config_line") + self.config_line.setObjectName("config_line") self.config_line.setFrameShape(QFrame.HLine) self.config_line.setFrameShadow(QFrame.Sunken) self.verticalLayout_4.addWidget(self.config_line) self.config_label = QLabel(self.scrollAreaWidgetContents) - self.config_label.setObjectName(u"config_label") + self.config_label.setObjectName("config_label") self.config_label.setMaximumSize(QSize(350, 16777215)) self.config_label.setWordWrap(True) @@ -357,11 +383,13 @@ def setupUi(self, TankDialog): self.config_layout = QVBoxLayout() self.config_layout.setSpacing(20) - self.config_layout.setObjectName(u"config_layout") + self.config_layout.setObjectName("config_layout") self.verticalLayout_4.addLayout(self.config_layout) - self.verticalSpacer_2 = QSpacerItem(328, 0, QSizePolicy.Minimum, QSizePolicy.Expanding) + self.verticalSpacer_2 = QSpacerItem( + 328, 0, QSizePolicy.Minimum, QSizePolicy.Expanding + ) self.verticalLayout_4.addItem(self.verticalSpacer_2) @@ -380,41 +408,94 @@ def setupUi(self, TankDialog): self.stackedWidget.setCurrentIndex(0) QMetaObject.connectSlotsByName(TankDialog) + # setupUi def retranslateUi(self, TankDialog): - TankDialog.setWindowTitle(QCoreApplication.translate("TankDialog", u"Dialog", None)) + TankDialog.setWindowTitle( + QCoreApplication.translate("TankDialog", "Dialog", None) + ) self.top_group.setTitle("") self.tank_logo.setText("") - self.label.setText(QCoreApplication.translate("TankDialog", u"TextLabel", None)) -#if QT_CONFIG(tooltip) - self.lbl_context.setToolTip(QCoreApplication.translate("TankDialog", u"foo bar", None)) -#endif // QT_CONFIG(tooltip) - self.lbl_context.setText(QCoreApplication.translate("TankDialog", u"Current Work Area:\n" -"TextLabel", None)) -#if QT_CONFIG(tooltip) - self.details_show.setToolTip(QCoreApplication.translate("TankDialog", u"Click for App Details", None)) -#endif // QT_CONFIG(tooltip) + self.label.setText(QCoreApplication.translate("TankDialog", "TextLabel", None)) + # if QT_CONFIG(tooltip) + self.lbl_context.setToolTip( + QCoreApplication.translate("TankDialog", "foo bar", None) + ) + # endif // QT_CONFIG(tooltip) + self.lbl_context.setText( + QCoreApplication.translate( + "TankDialog", "Current Work Area:\n" "TextLabel", None + ) + ) + # if QT_CONFIG(tooltip) + self.details_show.setToolTip( + QCoreApplication.translate("TankDialog", "Click for App Details", None) + ) + # endif // QT_CONFIG(tooltip) self.details_show.setText("") -#if QT_CONFIG(tooltip) - self.details_hide.setToolTip(QCoreApplication.translate("TankDialog", u"Hide App Details", None)) -#endif // QT_CONFIG(tooltip) + # if QT_CONFIG(tooltip) + self.details_hide.setToolTip( + QCoreApplication.translate("TankDialog", "Hide App Details", None) + ) + # endif // QT_CONFIG(tooltip) self.details_hide.setText("") self.page_2_group.setTitle("") self.label_3.setText("") self.gradient.setTitle("") self.app_icon.setText("") - self.app_name.setText(QCoreApplication.translate("TankDialog", u"Publish And Snapshot", None)) - self.app_description.setText(QCoreApplication.translate("TankDialog", u"Tools to see what is out of date in your scene etc etc.", None)) - self.app_tech_details.setText(QCoreApplication.translate("TankDialog", u"tk-multi-snapshot, v1.2.3", None)) - self.btn_documentation.setText(QCoreApplication.translate("TankDialog", u"Documentation", None)) - self.btn_support.setText(QCoreApplication.translate("TankDialog", u"Help && Support", None)) - self.label_5.setText(QCoreApplication.translate("TankDialog", u"Your Current Work Area", None)) - self.app_work_area_info.setText(QCoreApplication.translate("TankDialog", u"TextLabel", None)) - self.btn_file_system.setText(QCoreApplication.translate("TankDialog", u"Jump to File System", None)) - self.btn_shotgun.setText(QCoreApplication.translate("TankDialog", u"Jump to Flow Production Tracking", None)) - self.app_work_area_info_2.setText(QCoreApplication.translate("TankDialog", u"If you are making changes to configuration or code, use the reload button to quickly load your changes in without having to restart:", None)) - self.btn_reload.setText(QCoreApplication.translate("TankDialog", u"Reload Engine and Apps", None)) - self.config_header.setText(QCoreApplication.translate("TankDialog", u"Configuration", None)) - self.config_label.setText(QCoreApplication.translate("TankDialog", u"Below is a list of all the configuration settings for this app, as defined in your environment file:", None)) + self.app_name.setText( + QCoreApplication.translate("TankDialog", "Publish And Snapshot", None) + ) + self.app_description.setText( + QCoreApplication.translate( + "TankDialog", + "Tools to see what is out of date in your scene etc etc.", + None, + ) + ) + self.app_tech_details.setText( + QCoreApplication.translate("TankDialog", "tk-multi-snapshot, v1.2.3", None) + ) + self.btn_documentation.setText( + QCoreApplication.translate("TankDialog", "Documentation", None) + ) + self.btn_support.setText( + QCoreApplication.translate("TankDialog", "Help && Support", None) + ) + self.label_5.setText( + QCoreApplication.translate("TankDialog", "Your Current Work Area", None) + ) + self.app_work_area_info.setText( + QCoreApplication.translate("TankDialog", "TextLabel", None) + ) + self.btn_file_system.setText( + QCoreApplication.translate("TankDialog", "Jump to File System", None) + ) + self.btn_shotgun.setText( + QCoreApplication.translate( + "TankDialog", "Jump to Flow Production Tracking", None + ) + ) + self.app_work_area_info_2.setText( + QCoreApplication.translate( + "TankDialog", + "If you are making changes to configuration or code, use the reload button to quickly load your changes in without having to restart:", + None, + ) + ) + self.btn_reload.setText( + QCoreApplication.translate("TankDialog", "Reload Engine and Apps", None) + ) + self.config_header.setText( + QCoreApplication.translate("TankDialog", "Configuration", None) + ) + self.config_label.setText( + QCoreApplication.translate( + "TankDialog", + "Below is a list of all the configuration settings for this app, as defined in your environment file:", + None, + ) + ) + # retranslateUi diff --git a/python/tk-core/python/tank/platform/software_launcher.py b/python/tk-core/python/tank/platform/software_launcher.py index 39764c4e..96340d05 100644 --- a/python/tk-core/python/tank/platform/software_launcher.py +++ b/python/tk-core/python/tank/platform/software_launcher.py @@ -13,20 +13,18 @@ should implement. """ -import os -import sys import glob +import os import pprint +import sys from ..errors import TankError from ..log import LogManager +from ..util import ShotgunPath, is_windows +from ..util import sgre as re from ..util.loader import load_plugin from ..util.version import is_version_older -from ..util import ShotgunPath, is_windows, sgre as re - -from . import constants -from . import validation - +from . import constants, validation from .bundle import resolve_setting_value from .engine import get_env_and_descriptor_for_engine @@ -79,9 +77,7 @@ def create_engine_launcher(tk, context, engine_name, versions=None, products=Non on disk. """ # Get the engine environment and descriptor using engine.py code - (env, engine_descriptor) = get_env_and_descriptor_for_engine( - engine_name, tk, context - ) + env, engine_descriptor = get_env_and_descriptor_for_engine(engine_name, tk, context) # Make sure it exists locally if not engine_descriptor.exists_local(): diff --git a/python/tk-core/python/tank/platform/util.py b/python/tk-core/python/tank/platform/util.py index 903d918b..be642720 100644 --- a/python/tk-core/python/tank/platform/util.py +++ b/python/tk-core/python/tank/platform/util.py @@ -10,11 +10,11 @@ import logging -from .import_stack import ImportStack from ..errors import TankError -from .errors import TankContextChangeNotSupportedError, TankCurrentModuleNotFoundError -from .engine import current_engine, _restart_engine from ..log import LogManager +from .engine import _restart_engine, current_engine +from .errors import TankContextChangeNotSupportedError, TankCurrentModuleNotFoundError +from .import_stack import ImportStack def _get_current_bundle(): diff --git a/python/tk-core/python/tank/platform/validation.py b/python/tk-core/python/tank/platform/validation.py index e4a219fb..fb73ceed 100644 --- a/python/tk-core/python/tank/platform/validation.py +++ b/python/tk-core/python/tank/platform/validation.py @@ -12,15 +12,16 @@ App configuration and schema validation. """ + import os import sys -from . import constants from ..errors import TankError, TankNoDefaultValueError +from ..log import LogManager from ..template import TemplateString +from ..util.version import is_version_number, is_version_older +from . import constants from .bundle import resolve_default_value -from ..util.version import is_version_older, is_version_number -from ..log import LogManager # We're potentially running here in an environment with # no engine available via current_engine(), so we'll have @@ -354,7 +355,7 @@ def __validate_schema_type(self, settings_key, data_type): % (settings_key, self._display_name) ) - if not data_type in constants.TANK_SCHEMA_VALID_TYPES: + if data_type not in constants.TANK_SCHEMA_VALID_TYPES: params = (data_type, settings_key, self._display_name) raise TankError("Invalid type '%s' in schema '%s' for '%s'!" % params) @@ -406,14 +407,14 @@ def __validate_schema_value(self, settings_key, schema): def __validate_schema_list(self, settings_key, schema): # Check that the schema contains "values" - if not "values" in schema or type(schema["values"]) != dict: + if "values" not in schema or not isinstance(schema["values"], dict): params = (settings_key, self._display_name) raise TankError( "Missing or invalid 'values' dict in schema '%s' for '%s'!" % params ) # If there's an "allows_empty" key, it should be a bool - if "allows_empty" in schema and type(schema["allows_empty"]) != bool: + if "allows_empty" in schema and not isinstance(schema["allows_empty"], bool): params = (settings_key, self._display_name) raise TankError( "Invalid 'allows_empty' bool in schema '%s' for '%s'!" % params @@ -423,13 +424,13 @@ def __validate_schema_list(self, settings_key, schema): def __validate_schema_dict(self, settings_key, schema): # Check that if the schema contains "items" then it must be a dict - if "items" in schema and type(schema["items"]) != dict: + if "items" in schema and not isinstance(schema["items"], dict): params = (settings_key, self._display_name) raise TankError("Invalid 'items' dict in schema '%s' for '%s'!" % params) for key, value_schema in schema.get("items", {}).items(): # Check that the value is a dict, and validate it... - if type(value_schema) != dict: + if not isinstance(value_schema, dict): params = (key, settings_key, self._display_name) raise TankError("Invalid '%s' dict in schema '%s' for '%s'" % params) @@ -438,19 +439,21 @@ def __validate_schema_dict(self, settings_key, schema): def __validate_schema_template(self, settings_key, schema): # new style template def: if there is a fields key, it should be a str - if "fields" in schema and type(schema["fields"]) != str: + if "fields" in schema and not isinstance(schema["fields"], str): params = (settings_key, self._display_name) raise TankError("Invalid 'fields' string in schema '%s' for '%s'!" % params) # old-style - if there's a required_fields key, it should contain a list of strs. - if "required_fields" in schema and type(schema["required_fields"]) != list: + if "required_fields" in schema and not isinstance( + schema["required_fields"], list + ): params = (settings_key, self._display_name) raise TankError( "Invalid 'required_fields' list in schema '%s' for '%s'!" % params ) for field in schema.get("required_fields", []): - if type(field) != str: + if not isinstance(field, str): params = (field, settings_key, self._display_name) raise TankError( "Invalid 'required_fields' value '%s' in schema '%s' for '%s'!" @@ -458,9 +461,9 @@ def __validate_schema_template(self, settings_key, schema): ) # old-style - if there's an optional_fields key, it should contain a list of strs or be "*" - if "optional_fields" in schema and type(schema["optional_fields"]) == list: + if "optional_fields" in schema and isinstance(schema["optional_fields"], list): for field in schema.get("optional_fields", []): - if type(field) != str: + if not isinstance(field, str): params = (field, settings_key, self._display_name) raise TankError( "Invalid 'optional_fields' value '%s' in schema '%s' for '%s'!" @@ -473,7 +476,7 @@ def __validate_schema_template(self, settings_key, schema): ) # If there's an "allows_empty" key, it should be a bool - if "allows_empty" in schema and type(schema["allows_empty"]) != bool: + if "allows_empty" in schema and not isinstance(schema["allows_empty"], bool): params = (settings_key, self._display_name) raise TankError( "Invalid 'allows_empty' bool in schema '%s' for '%s'!" % params @@ -542,7 +545,7 @@ def __validate_settings_value(self, settings_key, schema, value): data_type = schema.get("type") # functor values which refer to a hook are never validated - if type(value) == str and value.startswith("hook:"): + if isinstance(value, str) and value.startswith("hook:"): return # shotgun filters can be a variety of formats so assume it is @@ -604,7 +607,7 @@ def __validate_settings_dict(self, settings_key, schema, value): items = schema.get("items", {}) for key, value_schema in items.items(): # Check for required keys - if not key in value: + if key not in value: params = (key, settings_key, self._display_name) raise TankError( "Missing required key '%s' in setting '%s' for '%s'" % params @@ -890,7 +893,7 @@ def __validate_new_style_template(self, cur_template, fields_str): # one or more mandatory issues. No point checking further return problems - if star == True: + if star: # means an open ended number of fields can be used. # no need to do more validation return problems diff --git a/python/tk-core/python/tank/template.py b/python/tk-core/python/tank/template.py index d5f41f04..5a76a62e 100644 --- a/python/tk-core/python/tank/template.py +++ b/python/tk-core/python/tank/template.py @@ -16,11 +16,12 @@ import os import sys -from . import templatekey +from tank.util import is_linux, is_macos, is_windows +from tank.util import sgre as re + +from . import constants, templatekey from .errors import TankError -from . import constants from .template_path_parser import TemplatePathParser -from tank.util import is_linux, is_macos, is_windows, sgre as re class Template(object): @@ -474,7 +475,7 @@ def validate(self, path, fields=None, skip_keys=None): :returns: True if the path is valid for this template :rtype: Bool """ - return self.validate_and_get_fields(path, fields, skip_keys) != None + return self.validate_and_get_fields(path, fields, skip_keys) is not None def get_fields(self, input_path, skip_keys=None): """ @@ -503,7 +504,7 @@ def get_fields(self, input_path, skip_keys=None): for ordered_keys, static_tokens in zip(self._ordered_keys, self._static_tokens): path_parser = TemplatePathParser(ordered_keys, static_tokens) fields = path_parser.parse_path(input_path, skip_keys) - if fields != None: + if fields is not None: break if fields is None: diff --git a/python/tk-core/python/tank/template_includes.py b/python/tk-core/python/tank/template_includes.py index 18e3e082..dc4d7a1c 100644 --- a/python/tk-core/python/tank/template_includes.py +++ b/python/tk-core/python/tank/template_includes.py @@ -28,10 +28,8 @@ """ -import os - -from .errors import TankError from . import constants +from .errors import TankError from .util import yaml_cache from .util.includes import resolve_include diff --git a/python/tk-core/python/tank/template_path_parser.py b/python/tk-core/python/tank/template_path_parser.py index f703617a..bab1e1ca 100644 --- a/python/tk-core/python/tank/template_path_parser.py +++ b/python/tk-core/python/tank/template_path_parser.py @@ -13,6 +13,7 @@ """ import os + from .errors import TankError diff --git a/python/tk-core/python/tank/templatekey.py b/python/tk-core/python/tank/templatekey.py index b165eaf3..c3276b9f 100644 --- a/python/tk-core/python/tank/templatekey.py +++ b/python/tk-core/python/tank/templatekey.py @@ -12,9 +12,9 @@ Classes for fields on TemplatePaths and TemplateStrings """ -import sys import collections.abc import datetime + from . import constants from .errors import TankError from .util import sgre as re @@ -514,7 +514,7 @@ def _as_string(self, value): if match is None: # no match. return empty string # validate should prevent this from happening - resolved_value = u"" + resolved_value = "" elif self._subset_format: # we have an explicit format string we want to apply to the match. @@ -1099,9 +1099,7 @@ def validate(self, value): error_msg += "Valid frame specs: %s\n" % str(self._frame_specs) error_msg += "Valid format strings: %s\n" % full_format_strings - if isinstance(value, str) and value.startswith( - self.FRAMESPEC_FORMAT_INDICATOR - ): + if isinstance(value, str) and value.startswith(self.FRAMESPEC_FORMAT_INDICATOR): # FORMAT: YXZ string - check that XYZ is in VALID_FORMAT_STRINGS pattern = self._extract_format_string(value) if pattern in self.VALID_FORMAT_STRINGS: @@ -1110,9 +1108,7 @@ def validate(self, value): self._last_error = error_msg return False - elif isinstance(value, str) and re.match( - self.FLAME_PATTERN_REGEX, value - ): + elif isinstance(value, str) and re.match(self.FLAME_PATTERN_REGEX, value): # value is matching the flame-style sequence pattern # [1234-5678] return True @@ -1131,16 +1127,12 @@ def validate(self, value): def _as_string(self, value): - if isinstance(value, str) and value.startswith( - self.FRAMESPEC_FORMAT_INDICATOR - ): + if isinstance(value, str) and value.startswith(self.FRAMESPEC_FORMAT_INDICATOR): # this is a FORMAT: XYZ - convert it to the proper resolved frame spec pattern = self._extract_format_string(value) return self._resolve_frame_spec(pattern, self.format_spec) - if isinstance(value, str) and re.match( - self.FLAME_PATTERN_REGEX, value - ): + if isinstance(value, str) and re.match(self.FLAME_PATTERN_REGEX, value): # this is a flame style sequence token [1234-56773] return value @@ -1167,9 +1159,7 @@ def _extract_format_string(self, value): """ Returns XYZ given the string "FORMAT: XYZ" """ - if isinstance(value, str) and value.startswith( - self.FRAMESPEC_FORMAT_INDICATOR - ): + if isinstance(value, str) and value.startswith(self.FRAMESPEC_FORMAT_INDICATOR): pattern = value.replace(self.FRAMESPEC_FORMAT_INDICATOR, "").strip() else: # passthrough diff --git a/python/tk-core/python/tank/util/__init__.py b/python/tk-core/python/tank/util/__init__.py index 80a7e284..6b304350 100644 --- a/python/tk-core/python/tank/util/__init__.py +++ b/python/tk-core/python/tank/util/__init__.py @@ -8,51 +8,42 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -from .platforms import is_windows, is_linux, is_macos -from .shotgun import register_publish -from .shotgun import resolve_publish_path -from .shotgun import find_publish -from .shotgun import download_url -from .shotgun import create_event_log_entry -from .shotgun import get_entity_type_display_name -from .shotgun import get_published_file_entity_type +from . import filesystem, json, pickle +from .environment import append_path_to_env_var, prepend_path_to_env_var +from .errors import ( + EnvironmentVariableFileLookupError, + PublishPathNotDefinedError, + PublishPathNotSupported, + PublishResolveError, + ShotgunAttachmentDownloadError, + ShotgunPublishError, + UnresolvableCoreConfigurationError, +) +from .local_file_storage import LocalFileStorageManager +from .login import get_current_user, get_shotgun_user +# DO keep the following two log_user_*_metric to preserve retro +# compatibility and prevent exception in legacy engine code. +from .metrics import EventMetric, log_user_activity_metric, log_user_attribute_metric +from .platforms import is_linux, is_macos, is_windows +from .shotgun import ( + create_event_log_entry, + download_url, + find_publish, + get_entity_type_display_name, + get_published_file_entity_type, + register_publish, + resolve_publish_path, +) +from .shotgun_entity import get_sg_entity_name_field +from .shotgun_path import ShotgunPath +from .storage_roots import StorageRoots +from .user_settings import UserSettings from .version import ( - is_version_older, is_version_newer, - is_version_older_or_equal, is_version_newer_or_equal, + is_version_older, + is_version_older_or_equal, suppress_known_deprecation, version_parse, ) - -from .shotgun_entity import get_sg_entity_name_field - -from .environment import append_path_to_env_var -from .environment import prepend_path_to_env_var - -from .login import get_shotgun_user -from .login import get_current_user - -# DO keep the following two log_user_*_metric to preserve retro -# compatibility and prevent exception in legacy engine code. -from .metrics import log_user_activity_metric -from .metrics import log_user_attribute_metric -from .metrics import EventMetric -from .shotgun_path import ShotgunPath - -from . import filesystem -from . import pickle -from . import json - -from .local_file_storage import LocalFileStorageManager - -from .errors import PublishResolveError -from .errors import UnresolvableCoreConfigurationError, ShotgunAttachmentDownloadError -from .errors import EnvironmentVariableFileLookupError, ShotgunPublishError -from .errors import PublishResolveError -from .errors import PublishPathNotDefinedError, PublishPathNotSupported - -from .user_settings import UserSettings - -from .storage_roots import StorageRoots diff --git a/python/tk-core/python/tank/util/environment.py b/python/tk-core/python/tank/util/environment.py index b572555d..a0cef9b6 100644 --- a/python/tk-core/python/tank/util/environment.py +++ b/python/tk-core/python/tank/util/environment.py @@ -13,6 +13,7 @@ """ import os + from .platforms import is_windows diff --git a/python/tk-core/python/tank/util/filesystem.py b/python/tk-core/python/tank/util/filesystem.py index 6a36d6e0..ab2c1742 100644 --- a/python/tk-core/python/tank/util/filesystem.py +++ b/python/tk-core/python/tank/util/filesystem.py @@ -11,15 +11,15 @@ Utility methods for manipulating files and folders """ +import datetime +import errno +import functools import os import re -import sys -import errno -import stat import shutil -import datetime -import functools +import stat import subprocess +import sys from contextlib import contextmanager from .. import LogManager diff --git a/python/tk-core/python/tank/util/includes.py b/python/tk-core/python/tank/util/includes.py index 1b3708f7..d9c39ede 100644 --- a/python/tk-core/python/tank/util/includes.py +++ b/python/tk-core/python/tank/util/includes.py @@ -8,14 +8,13 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. +import ntpath import os -import sys import posixpath -import ntpath -from .shotgun_path import ShotgunPath -from .platforms import is_windows from ..errors import TankError +from .platforms import is_windows +from .shotgun_path import ShotgunPath def _is_abs(path): diff --git a/python/tk-core/python/tank/util/json.py b/python/tk-core/python/tank/util/json.py index 896cd4e7..2d303766 100644 --- a/python/tk-core/python/tank/util/json.py +++ b/python/tk-core/python/tank/util/json.py @@ -14,8 +14,9 @@ # We need to add this to the file or the import json will reimport this # module instead of importing the global json module. from __future__ import absolute_import -import sys + import json +import sys from .unicode import ensure_contains_str diff --git a/python/tk-core/python/tank/util/loader.py b/python/tk-core/python/tank/util/loader.py index 3f81a0be..4b30b84b 100644 --- a/python/tk-core/python/tank/util/loader.py +++ b/python/tk-core/python/tank/util/loader.py @@ -13,13 +13,13 @@ """ -import sys import importlib.util -import traceback import inspect +import sys +import traceback -from ..errors import TankError from .. import LogManager +from ..errors import TankError log = LogManager.get_logger(__name__) @@ -57,8 +57,8 @@ def load_plugin(plugin_file, valid_base_class, alternate_base_classes=None): try: plugin_spec = importlib.util.spec_from_file_location(module_uid, plugin_file) module = importlib.util.module_from_spec(plugin_spec) - plugin_spec.loader.exec_module(module) sys.modules[module.__name__] = module + plugin_spec.loader.exec_module(module) except Exception: # log the full callstack to make sure that whatever the # calling code is doing, this error is logged to help @@ -66,7 +66,7 @@ def load_plugin(plugin_file, valid_base_class, alternate_base_classes=None): log.exception("Cannot load plugin file '%s'" % plugin_file) # dump out the callstack for this one -- to help people get good messages when there is a plugin error - (exc_type, exc_value, exc_traceback) = sys.exc_info() + exc_type, exc_value, exc_traceback = sys.exc_info() message = "" message += ( "Failed to load plugin %s. The following error was reported:\n" diff --git a/python/tk-core/python/tank/util/local_file_storage.py b/python/tk-core/python/tank/util/local_file_storage.py index 6215586e..ff805a6c 100644 --- a/python/tk-core/python/tank/util/local_file_storage.py +++ b/python/tk-core/python/tank/util/local_file_storage.py @@ -57,10 +57,10 @@ class LocalFileStorageManager(object): """ # generation of path structures - (CORE_V17, CORE_V18) = range(2) + CORE_V17, CORE_V18 = range(2) # supported types of paths - (LOGGING, CACHE, PERSISTENT, PREFERENCES) = range(4) + LOGGING, CACHE, PERSISTENT, PREFERENCES = range(4) @classmethod def get_global_root(cls, path_type, generation=CORE_V18): diff --git a/python/tk-core/python/tank/util/login.py b/python/tk-core/python/tank/util/login.py index 442cb377..31e99232 100644 --- a/python/tk-core/python/tank/util/login.py +++ b/python/tk-core/python/tank/util/login.py @@ -14,7 +14,6 @@ """ import os -import sys from . import constants from .platforms import is_windows @@ -34,7 +33,7 @@ def get_login_name(): pwd_entry = pwd.getpwuid(os.geteuid()) return pwd_entry[0] - except: + except Exception: return None diff --git a/python/tk-core/python/tank/util/metrics.py b/python/tk-core/python/tank/util/metrics.py index 022de787..521f183c 100644 --- a/python/tk-core/python/tank/util/metrics.py +++ b/python/tk-core/python/tank/util/metrics.py @@ -72,7 +72,7 @@ def get_darwin_version(cls): # For macOS / OSX we keep only the Major.minor os_version = re.findall(r"\d*\.\d*", raw_version_str)[0] - except: + except Exception: pass return os_version @@ -96,7 +96,7 @@ def get_linux_version(cls): major_version_str = re.findall(r"\d*", raw_version_str)[0] os_version = "%s %s" % (distribution, major_version_str) - except: + except Exception: pass return os_version @@ -116,7 +116,7 @@ def get_windows_version(cls): # as it returns a friendly name e.g: XP, 7, 10 etc. os_version = platform.release() - except: + except Exception: pass return os_version @@ -161,7 +161,7 @@ def get_platform_info(cls): else: os_info["OS"] = "Unsupported system: (%s)" % (system) - except: + except Exception: # On any exception we fallback to default value pass @@ -243,7 +243,7 @@ def log(self, metric, log_once=False): # remember that we've logged this one already self.__logged_metrics.add(metric_identifier) - except: + except Exception: pass finally: self._lock.release() @@ -275,7 +275,7 @@ def get_metrics(self, count=None): # would be nice to be able to pop N from deque. oh well. metrics = [self._queue.popleft() for i in range(0, count)] - except: + except Exception: pass finally: self._lock.release() @@ -434,7 +434,7 @@ def run(self): else: break - except Exception as e: + except Exception: pass finally: # wait, checking for halt event before more processing @@ -790,7 +790,7 @@ def log(cls, group, name, properties=None, log_once=False, bundle=None): from sgtk.platform.util import current_bundle bundle = current_bundle() - except: + except Exception: pass if not bundle: @@ -800,7 +800,7 @@ def log(cls, group, name, properties=None, log_once=False, bundle=None): from ..platform.engine import current_engine bundle = current_engine() - except: + except Exception: # Bailing out trying to guess bundle pass diff --git a/python/tk-core/python/tank/util/metrics_cache.py b/python/tk-core/python/tank/util/metrics_cache.py index 4e5ec77d..9693addd 100644 --- a/python/tk-core/python/tank/util/metrics_cache.py +++ b/python/tk-core/python/tank/util/metrics_cache.py @@ -29,9 +29,8 @@ import json import os -from . import metrics - from .. import LogManager +from . import metrics logger = LogManager.get_logger(__name__) @@ -97,7 +96,7 @@ def consume(): logger.debug("Invalid cached metric format") continue - (args, kwargs) = data + args, kwargs = data if not isinstance(args, list) or not isinstance(kwargs, dict): logger.debug("Invalid cached metric format") continue diff --git a/python/tk-core/python/tank/util/process.py b/python/tk-core/python/tank/util/process.py index 34fd5e86..6fd585e0 100644 --- a/python/tk-core/python/tank/util/process.py +++ b/python/tk-core/python/tank/util/process.py @@ -8,11 +8,11 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -import subprocess import pprint +import subprocess -from .platforms import is_windows from ..log import LogManager +from .platforms import is_windows logger = LogManager.get_logger(__name__) diff --git a/python/tk-core/python/tank/util/pyside2_patcher.py b/python/tk-core/python/tank/util/pyside2_patcher.py index 7ef8ae83..974269fc 100644 --- a/python/tk-core/python/tank/util/pyside2_patcher.py +++ b/python/tk-core/python/tank/util/pyside2_patcher.py @@ -12,10 +12,10 @@ PySide 2 backwards compatibility layer for use with PySide 1 code. """ -import os -import sys import functools +import os import subprocess +import sys import types import warnings import webbrowser @@ -328,7 +328,7 @@ def openUrl(cls, url): # returns False or raises some error. try: return webbrowser.open_new_tab(url.toString().encode("utf-8")) - except: + except Exception: return False @classmethod diff --git a/python/tk-core/python/tank/util/pyside6_patcher.py b/python/tk-core/python/tank/util/pyside6_patcher.py index 5aed21d8..0cef38f0 100644 --- a/python/tk-core/python/tank/util/pyside6_patcher.py +++ b/python/tk-core/python/tank/util/pyside6_patcher.py @@ -91,7 +91,7 @@ def _patch_QTextCodec(cls, QtCore): QTextCodec has been removed in Qt6. Using this class will do nothing. """ - class QTextCodec(): + class QTextCodec: @staticmethod def codecForName(name): return None @@ -135,7 +135,6 @@ def grabWindow(window=0, x=0, y=0, width=-1, height=-1): screen = QtGui.QApplication.primaryScreen() return screen.grabWindow(window, x, y, width, height) - QtGui.QPixmap = QPixmap @classmethod @@ -153,7 +152,6 @@ def pixmap(self, *args, **kwargs): QtGui.QIcon.pixmap = pixmap - @classmethod def _patch_QLabel(cls, QtGui): """ @@ -229,11 +227,11 @@ def emit(screen_index): screens = QtGui.QGuiApplication.screens() screen = screens[screen_index] screen.geometryChanged.emit() - except: + except Exception: pass - original_QScreen_availableGeometry = QtGui.QScreen.availableGeometry + def availableGeometry(self, arg__1=None): """ Patch QScreen to also act as QDesktopWidget. @@ -298,7 +296,9 @@ def _patch_QOpenGLContext(cls, QtGui): def versionFunctions(self, version_profile=None): if version_profile: - return QtGui.QOpenGLVersionFunctionsFactory.get(versionProfile=version_profile, context=self) + return QtGui.QOpenGLVersionFunctionsFactory.get( + versionProfile=version_profile, context=self + ) return QtGui.QOpenGLVersionFunctionsFactory.get(context=self) QtGui.QOpenGLContext.versionFunctions = versionFunctions @@ -352,18 +352,39 @@ def __init__(self, *args, **kwargs): if case_sensitivity is None: original_QRegularExpression.__init__(self, args[0]) else: - if case_sensitivity == original_QRegularExpression.CaseInsensitiveOption: + if ( + case_sensitivity + == original_QRegularExpression.CaseInsensitiveOption + ): opts = original_QRegularExpression.CaseInsensitiveOption else: opts = original_QRegularExpression.NoPatternOption - original_QRegularExpression.__init__(self, args[0], options=opts) + original_QRegularExpression.__init__( + self, args[0], options=opts + ) - self.isEmpty = lambda *args, **kwargs: QRegularExpression.isEmpty(self, *args, **kwargs) - self.indexIn = lambda *args, **kwargs: QRegularExpression.indexIn(self, *args, **kwargs) - self.matchedLength = lambda *args, **kwargs: QRegularExpression.matchedLength(self, *args, **kwargs) - self.setCaseSensitivity = lambda *args, **kwargs: QRegularExpression.setCaseSensitivity(self, *args, **kwargs) - self.pos = lambda *args, **kwargs: QRegularExpression.pos(self, *args, **kwargs) - self.cap = lambda *args, **kwargs: QRegularExpression.cap(self, *args, **kwargs) + self.isEmpty = lambda *args, **kwargs: QRegularExpression.isEmpty( + self, *args, **kwargs + ) + self.indexIn = lambda *args, **kwargs: QRegularExpression.indexIn( + self, *args, **kwargs + ) + self.matchedLength = ( + lambda *args, **kwargs: QRegularExpression.matchedLength( + self, *args, **kwargs + ) + ) + self.setCaseSensitivity = ( + lambda *args, **kwargs: QRegularExpression.setCaseSensitivity( + self, *args, **kwargs + ) + ) + self.pos = lambda *args, **kwargs: QRegularExpression.pos( + self, *args, **kwargs + ) + self.cap = lambda *args, **kwargs: QRegularExpression.cap( + self, *args, **kwargs + ) @staticmethod def isEmpty(re): @@ -426,7 +447,9 @@ def cap(re, n): QtCore.QRegularExpression.isEmpty = QRegularExpression.isEmpty QtCore.QRegularExpression.indexIn = QRegularExpression.indexIn QtCore.QRegularExpression.matchedLength = QRegularExpression.matchedLength - QtCore.QRegularExpression.setCaseSensitivity = QRegularExpression.setCaseSensitivity + QtCore.QRegularExpression.setCaseSensitivity = ( + QRegularExpression.setCaseSensitivity + ) QtCore.QRegularExpression.pos = QRegularExpression.pos # This pattern matching flag is obsolete now. @@ -452,9 +475,9 @@ def flush(): @classmethod def patch( - cls, - QtWebEngineWidgets, - QtWebEngineCore, + cls, + QtWebEngineWidgets, + QtWebEngineCore, ): """ Patch the PySide6 modules, classes and function to conform to the PySide interface. @@ -475,8 +498,8 @@ def patch( from PySide6 import ( QtCore, QtGui, - QtWidgets, QtOpenGL, + QtWidgets, ) # First create new modules to act as the PySide modules @@ -546,8 +569,12 @@ def patch( cls._patch_QRegularExpression(qt_core_shim) qt_core_shim.QRegExp = qt_core_shim.QRegularExpression # Rename RegExp functions to RegularExpression - qt_gui_shim.QSortFilterProxyModel.filterRegExp = qt_gui_shim.QSortFilterProxyModel.filterRegularExpression - qt_gui_shim.QSortFilterProxyModel.setFilterRegExp = qt_gui_shim.QSortFilterProxyModel.setFilterRegularExpression + qt_gui_shim.QSortFilterProxyModel.filterRegExp = ( + qt_gui_shim.QSortFilterProxyModel.filterRegularExpression + ) + qt_gui_shim.QSortFilterProxyModel.setFilterRegExp = ( + qt_gui_shim.QSortFilterProxyModel.setFilterRegularExpression + ) # Patch the QCoreApplication.flush() method to ensure compatibility with code # that expects this method, which is marked as obsolete. @@ -588,7 +615,9 @@ def patch( # The default timeout parameter removed. This param, if given, will be ignored. It will # always timeout after 100 ms # https://doc.qt.io/qt-6/widgets-changes-qt6.html#the-qabstractbutton-class - qt_gui_shim.QAbstractButton.animateClick = lambda self, msec=0: self.animateClick() + qt_gui_shim.QAbstractButton.animateClick = ( + lambda self, msec=0: self.animateClick() + ) # Changes to QFont # https://doc.qt.io/qt-6/gui-changes-qt6.html#the-qfont-class @@ -596,7 +625,9 @@ def patch( qt_gui_shim.QFont.setWeight = qt_gui_shim.QFont.setLegacyWeight # QHeaderView method rename - qt_gui_shim.QHeaderView.setResizeMode = qt_gui_shim.QHeaderView.setSectionResizeMode + qt_gui_shim.QHeaderView.setResizeMode = ( + qt_gui_shim.QHeaderView.setSectionResizeMode + ) # QPainter HighQualityAntialiasing is obsolete. Use Antiasliasing instead. # https://doc.qt.io/qt-5/qpainter.html#RenderHint-enum @@ -609,6 +640,8 @@ def patch( if qt_web_engine_widgets_shim: # QtWwebEngineWidgets qt_web_engine_widgets_shim.QWebEnginePage = QtWebEngineCore.QWebEnginePage - qt_web_engine_widgets_shim.QWebEngineProfile = QtWebEngineCore.QWebEngineProfile + qt_web_engine_widgets_shim.QWebEngineProfile = ( + QtWebEngineCore.QWebEngineProfile + ) return qt_core_shim, qt_gui_shim, qt_web_engine_widgets_shim diff --git a/python/tk-core/python/tank/util/qt_importer.py b/python/tk-core/python/tank/util/qt_importer.py index b0fc282e..297059a3 100644 --- a/python/tk-core/python/tank/util/qt_importer.py +++ b/python/tk-core/python/tank/util/qt_importer.py @@ -148,7 +148,6 @@ def _import_module_by_name(self, parent_module_name, module_name): except Exception as e: logger.debug("Unable to import module '%s': %s", module_name, e) - def _import_pyside2(self): """ This will be called at initialization to discover every PySide 2 modules. @@ -159,8 +158,8 @@ def _import_pyside2(self): # throw an import error which will be handled by the calling code. Note that PySide2 can be # imported even if the Qt binaries are missing, so it's better to try importing QtCore for # testing. - from PySide2 import QtCore import shiboken2 + from PySide2 import QtCore # List of all Qt 5 modules. sub_modules = [ @@ -234,8 +233,9 @@ def _import_pyside2_as_pyside(self): :returns: The (binding name, binding version, modules) tuple. """ import PySide2 - from PySide2 import QtCore, QtGui, QtWidgets import shiboken2 + from PySide2 import QtCore, QtGui, QtWidgets + from .pyside2_patcher import PySide2Patcher QtCore, QtGui = PySide2Patcher.patch(QtCore, QtGui, QtWidgets, PySide2) @@ -269,6 +269,7 @@ def _import_pyside6_as_pyside(self): # pragma: no cover import PySide6 import shiboken6 + from .pyside6_patcher import PySide6Patcher QtWebEngineWidgets, QtWebEngineCore = None, None diff --git a/python/tk-core/python/tank/util/resources/no_preview.jpg b/python/tk-core/python/tank/util/resources/no_preview.jpg deleted file mode 100644 index c5239376..00000000 Binary files a/python/tk-core/python/tank/util/resources/no_preview.jpg and /dev/null differ diff --git a/python/tk-core/python/tank/util/sgre.py b/python/tk-core/python/tank/util/sgre.py index 4283058a..424ee385 100644 --- a/python/tk-core/python/tank/util/sgre.py +++ b/python/tk-core/python/tank/util/sgre.py @@ -13,28 +13,28 @@ some functionality. """ +# For Python 3, we'll wrap the re functions to inject the ASCII flag when necessary +# to maintain the previous behavior. +import re as _re +import typing as _typing + # Import constants and functions that won't be wrapped -from re import ( +from re import LOCALE # noqa import into namespace +from re import escape # noqa import into namespace +from re import ( # noqa: F401 import into namespace DEBUG, - I, + DOTALL, IGNORECASE, + MULTILINE, + UNICODE, + VERBOSE, + I, L, - LOCALE, # noqa import into namespace M, - MULTILINE, S, - DOTALL, U, - UNICODE, X, - VERBOSE, ) -from re import escape # noqa import into namespace - -# For Python 3, we'll wrap the re functions to inject the ASCII flag when necessary -# to maintain the previous behavior. -import re as _re -import typing as _typing def _re_wrap(fn, flags_arg_position): diff --git a/python/tk-core/python/tank/util/shotgun/__init__.py b/python/tk-core/python/tank/util/shotgun/__init__.py index 1d72bbd6..43490691 100644 --- a/python/tk-core/python/tank/util/shotgun/__init__.py +++ b/python/tk-core/python/tank/util/shotgun/__init__.py @@ -10,25 +10,23 @@ from .connection import ( - get_project_name_studio_hook_location, + create_sg_connection, get_associated_sg_base_url, get_associated_sg_config_data, get_deferred_sg_connection, + get_project_name_studio_hook_location, get_sg_connection, - create_sg_connection, -) - -from .publish_util import ( - get_entity_type_display_name, - find_publish, - create_event_log_entry, - get_published_file_entity_type, ) - -from .publish_creation import register_publish -from .publish_resolve import resolve_publish_path from .download import ( - download_url, download_and_unpack_attachment, download_and_unpack_url, + download_url, +) +from .publish_creation import register_publish +from .publish_resolve import resolve_publish_path +from .publish_util import ( + create_event_log_entry, + find_publish, + get_entity_type_display_name, + get_published_file_entity_type, ) diff --git a/python/tk-core/python/tank/util/shotgun/connection.py b/python/tk-core/python/tank/util/shotgun/connection.py index dda58b7c..97acd236 100644 --- a/python/tk-core/python/tank/util/shotgun/connection.py +++ b/python/tk-core/python/tank/util/shotgun/connection.py @@ -372,7 +372,6 @@ def get_sg_connection(): :return: PTR API handle """ - global _g_sg_cached_connections sg = getattr(_g_sg_cached_connections, "sg", None) if sg is None: diff --git a/python/tk-core/python/tank/util/shotgun/download.py b/python/tk-core/python/tank/util/shotgun/download.py index 9887f71d..7e73ed10 100644 --- a/python/tk-core/python/tank/util/shotgun/download.py +++ b/python/tk-core/python/tank/util/shotgun/download.py @@ -13,7 +13,6 @@ """ import os -import sys import tempfile import time import urllib.parse @@ -181,7 +180,9 @@ def download_and_unpack_attachment( ) -def download_and_unpack_url(sg, url, target, retries=5, auto_detect_bundle=False, headers=None): +def download_and_unpack_url( + sg, url, target, retries=5, auto_detect_bundle=False, headers=None +): """ Downloads the content from the provided url, assumes it is a zip file and attempts to unpack it into the given location. @@ -197,7 +198,9 @@ def download_and_unpack_url(sg, url, target, retries=5, auto_detect_bundle=False the bundle in a subfolder, this should be correctly unfolded. :raises: ShotgunAttachmentDownloadError on failure """ - return _download_and_unpack(sg, target, retries, auto_detect_bundle, url=url, headers=headers or {}) + return _download_and_unpack( + sg, target, retries, auto_detect_bundle, url=url, headers=headers or {} + ) @LogManager.log_timing diff --git a/python/tk-core/python/tank/util/shotgun/publish_creation.py b/python/tk-core/python/tank/util/shotgun/publish_creation.py index c8670cc6..7eaa6483 100644 --- a/python/tk-core/python/tank/util/shotgun/publish_creation.py +++ b/python/tk-core/python/tank/util/shotgun/publish_creation.py @@ -311,24 +311,17 @@ def register_publish(tk, context, path, name, version_number, **kwargs): ) # entity - if update_entity_thumbnail == True and context.entity is not None: + if update_entity_thumbnail is True and context.entity is not None: tk.shotgun.upload_thumbnail( context.entity["type"], context.entity["id"], thumbnail_path ) # task - if update_task_thumbnail == True and task is not None: + if update_task_thumbnail and task is not None: tk.shotgun.upload_thumbnail("Task", task["id"], thumbnail_path) else: - # no thumbnail found - instead use the default one - this_folder = os.path.abspath(os.path.dirname(__file__)) - no_thumb = os.path.join( - this_folder, os.path.pardir, "resources", "no_preview.jpg" - ) - tk.shotgun.upload_thumbnail( - published_file_entity_type, entity.get("id"), no_thumb - ) + log.debug("Publish: No thumbnail provided, skipping thumbnail upload.") # register dependencies log.debug("Publish: Register dependencies") diff --git a/python/tk-core/python/tank/util/shotgun/publish_util.py b/python/tk-core/python/tank/util/shotgun/publish_util.py index baa7c67d..a135d350 100644 --- a/python/tk-core/python/tank/util/shotgun/publish_util.py +++ b/python/tk-core/python/tank/util/shotgun/publish_util.py @@ -13,9 +13,8 @@ """ from ...log import LogManager +from .. import constants, login from ..shotgun_path import ShotgunPath -from .. import constants -from .. import login log = LogManager.get_logger(__name__) @@ -50,7 +49,7 @@ def get_entity_type_display_name(tk, entity_type_code): try: if entity_type_code in schema_data: display_name = schema_data[entity_type_code]["name"]["value"] - except: + except Exception: pass return display_name diff --git a/python/tk-core/python/tank/util/shotgun_entity.py b/python/tk-core/python/tank/util/shotgun_entity.py index b0d40ae2..901bf389 100644 --- a/python/tk-core/python/tank/util/shotgun_entity.py +++ b/python/tk-core/python/tank/util/shotgun_entity.py @@ -12,8 +12,9 @@ Utilities relating to Shotgun entities """ -from . import constants, sgre as re from ..errors import TankError +from . import constants +from . import sgre as re # A dictionary for Shotgun entities which do not store their name # in the standard "code" field. @@ -165,7 +166,7 @@ def __init__(self, tk, entity_type, field_name_expr): regex_obj = None if ":" in field_token_expression: - (full_field_name, regex) = field_token_expression.split(":", 1) + full_field_name, regex = field_token_expression.split(":", 1) try: regex_obj = re.compile(regex, re.UNICODE) @@ -428,7 +429,7 @@ def _process_regex(self, value, regex_obj): match = regex_obj.match(value_to_convert) if match is None: # no match. return empty string - resolved_value = u"" + resolved_value = "" else: # we have a match object. concatenate the groups resolved_value = "".join(match.groups()) diff --git a/python/tk-core/python/tank/util/shotgun_path.py b/python/tk-core/python/tank/util/shotgun_path.py index 1b7ee3b6..bbb783d5 100644 --- a/python/tk-core/python/tank/util/shotgun_path.py +++ b/python/tk-core/python/tank/util/shotgun_path.py @@ -9,6 +9,7 @@ # not expressly granted therein are reserved by Shotgun Software Inc. import sys + from .platforms import is_linux, is_macos, is_windows diff --git a/python/tk-core/python/tank/util/storage_roots.py b/python/tk-core/python/tank/util/storage_roots.py index 64cfda36..0d1c3fc1 100644 --- a/python/tk-core/python/tank/util/storage_roots.py +++ b/python/tk-core/python/tank/util/storage_roots.py @@ -14,10 +14,7 @@ from .. import LogManager from ..errors import TankError - -from . import filesystem -from . import ShotgunPath -from . import yaml_cache +from . import ShotgunPath, filesystem, yaml_cache log = LogManager.get_logger(__name__) @@ -144,7 +141,7 @@ def write(cls, sg_connection, config_folder, storage_roots): the required roots. """ - (local_storage_lookup, unmapped_roots) = storage_roots.get_local_storages( + local_storage_lookup, unmapped_roots = storage_roots.get_local_storages( sg_connection ) diff --git a/python/tk-core/python/tank/util/user_settings.py b/python/tk-core/python/tank/util/user_settings.py index 6e9edbd5..148b3fb6 100644 --- a/python/tk-core/python/tank/util/user_settings.py +++ b/python/tk-core/python/tank/util/user_settings.py @@ -15,13 +15,12 @@ import configparser import os -from .local_file_storage import LocalFileStorageManager -from .errors import EnvironmentVariableFileLookupError, TankError from .. import LogManager +from .errors import EnvironmentVariableFileLookupError, TankError +from .local_file_storage import LocalFileStorageManager from .singleton import Singleton from .system_settings import SystemSettings - logger = LogManager.get_logger(__name__) diff --git a/python/tk-core/python/tank/util/yaml_cache.py b/python/tk-core/python/tank/util/yaml_cache.py index 61275f4e..9bfc6042 100644 --- a/python/tk-core/python/tank/util/yaml_cache.py +++ b/python/tk-core/python/tank/util/yaml_cache.py @@ -13,12 +13,13 @@ unless it's changed on disk. """ -import os import copy +import os import threading from tank_vendor import yaml -from ..errors import TankError, TankUnreadableFileError, TankFileDoesNotExistError + +from ..errors import TankError, TankFileDoesNotExistError, TankUnreadableFileError class CacheItem(object): diff --git a/python/tk-core/python/tank/util/zip.py b/python/tk-core/python/tank/util/zip.py index a92964f7..32f6d3f9 100644 --- a/python/tk-core/python/tank/util/zip.py +++ b/python/tk-core/python/tank/util/zip.py @@ -10,8 +10,9 @@ import os import zipfile -from . import filesystem + from .. import LogManager +from . import filesystem log = LogManager.get_logger(__name__) diff --git a/python/tk-core/python/tank_vendor/__init__.py b/python/tk-core/python/tank_vendor/__init__.py index 6fe433ca..6ab8f1f1 100644 --- a/python/tk-core/python/tank_vendor/__init__.py +++ b/python/tk-core/python/tank_vendor/__init__.py @@ -12,9 +12,14 @@ tank_vendor module - Third-party dependency management for Shotgun Toolkit. This module handles loading and importing third-party Python packages from -version-specific ZIP archives (pkgs.zip). It provides: - -1. Auto-discovery of packages in pkgs.zip +ZIP archives. It provides: + +1. Auto-discovery of packages in two locations: + - requirements/./pkgs.zip (per-Python-version; present in + source checkouts, absent when + tk-core is pip-installed and + dependencies come from the env) + - requirements/any/*.zip (Python-version-independent, optional) 2. Lazy import hook for transparent tank_vendor.* namespace aliasing 3. Package-specific patches (e.g., SSL certificate handling for shotgun_api3) @@ -22,16 +27,24 @@ # Direct imports work automatically: from tank_vendor import yaml from tank_vendor.shotgun_api3 import Shotgun + from tank_vendor import flow_data_sdk # Submodule imports work via lazy loading: from tank_vendor.shotgun_api3.lib import httplib2 + from tank_vendor.flow_data_sdk.base import client # Mock.patch works seamlessly: mock.patch("tank_vendor.shotgun_api3.Shotgun.find") -Supported Python versions: 3.7+ +Shared zips in requirements/any/ are loaded after pkgs.zip, so per-version +pinned packages take precedence over anything in the shared directory. +Packages whose top-level name is already registered are skipped with a +warning. + +Supported Python versions: 3.9+ """ +import os import pathlib import sys import warnings @@ -187,6 +200,12 @@ def _patched_get_certs_file(ca_certs=None): # If ca_certs explicitly provided, use original behavior if ca_certs is not None: return _original_get_certs_file(ca_certs) + # Preserve shotgun_api3's documented SHOTGUN_API_CACERTS override. + # Without this branch, a network-hosted extracted cert_file can be + # dramatically slower to open than a local override (see SG-44256). + environment_certs = os.environ.get("SHOTGUN_API_CACERTS") + if environment_certs: + return environment_certs # Otherwise return extracted certificate path instead of ZIP path return str(cert_file) @@ -199,7 +218,7 @@ def _install_import_hook(): Install a lazy import hook that redirects tank_vendor.* imports to real packages. This enables transparent namespace aliasing, allowing code to use tank_vendor.package - while the actual package is loaded from pkgs.zip without the tank_vendor prefix. + while the actual package is loaded from a ZIP without the tank_vendor prefix. Examples: from tank_vendor.shotgun_api3.lib import httplib2 @@ -227,121 +246,238 @@ def _install_import_hook(): sys.meta_path.insert(0, sys._tank_vendor_meta_finder) -# ============================================================================ -# MAIN INITIALIZATION: Load third-party packages from pkgs.zip -# ============================================================================ +def _discover_top_level_packages(zip_path): + """ + Return the set of top-level importable package names inside a zip. + + Filters out: + - .dist-info: Package metadata directories (still in zip for importlib.metadata, + but not importable as packages) + - __pycache__: Python bytecode cache + - .pyd/.so/.dylib/.dll: Platform-specific binary extensions + - _*: Private/internal modules (e.g., _ruamel_yaml.cp311-win_amd64.pyd) + """ + with zipfile.ZipFile(zip_path, "r") as zf: + top_level = set() + for name in zf.namelist(): + parts = name.split("/") + if parts[0] and not parts[0].endswith(".py"): + top_level.add(parts[0]) + elif parts[0].endswith(".py") and parts[0] != "__pycache__": + top_level.add(parts[0][:-3]) + + return { + pkg + for pkg in top_level + if not pkg.endswith(".dist-info") + and pkg != "__pycache__" + and not pkg.endswith(".py") + and not pkg.endswith(".pyd") + and not pkg.endswith(".so") + and not pkg.endswith(".dylib") + and not pkg.endswith(".dll") + and not pkg.startswith("_") + } + + +def _load_packages_from_zip(zip_path): + """ + Validate a vendor zip, insert it at the front of sys.path, and register + its top-level packages under the tank_vendor namespace. -# Construct path to Python version-specific pkgs.zip containing third-party dependencies. -# Path structure: /requirements/./pkgs.zip -# Example: requirements/3.11/pkgs.zip for Python 3.11 -pkgs_zip_path = ( - pathlib.Path(__file__).resolve().parent.parent.parent - / "requirements" - / f"{sys.version_info.major}.{sys.version_info.minor}" - / "pkgs.zip" -) + Missing or unreadable zips are tolerated (return False, with a warning + for unreadable). A wholesale failure during package discovery/import + raises RuntimeError after cleaning the zip path off sys.path. Individual + package import failures inside the zip warn and are skipped. + + Each zip is always inserted at sys.path[0], so the LAST zip loaded ends + up at the front of sys.path. Collisions are resolved by sys.modules + (first-registered wins), independent of sys.path order — see callers + for the intentional load order. + + Args: + zip_path: pathlib.Path to the zip file. + + Returns: + True if the zip was successfully loaded, False if it was missing + or unreadable. + """ + # Step 1: Validate the zip exists, is a file (not a directory of extracted + # contents, as some CI environments produce), and can be opened as a ZIP. + # Missing zips are silent (pip-installed setups have no pkgs.zip). Unreadable + # zips warn so the failure mode is visible, but don't fail the import. + if not zip_path.exists() or not zip_path.is_file(): + return False + + try: + with zipfile.ZipFile(zip_path, "r") as zf: + zf.namelist() + except (zipfile.BadZipFile, OSError, IOError) as e: + warnings.warn( + f"Failed to load packages from {zip_path}: {e}. " + "Any dependencies it would have provided will need to be resolved " + "from the Python environment, or will fail at import time.", + RuntimeWarning, + stacklevel=2, + ) + return False + + # Step 2: Put the zip on sys.path so Python can import directly from it. + # Insertion ordering is load-bearing: importlib.metadata.version() resolves + # dist-info inside a zip only after the zip is on sys.path. + sys.path.insert(0, str(zip_path)) -# Validate pkgs.zip before attempting to load from it. -# This provides backward compatibility for: -# - Installations using old vendored copies -# - Temporary locations without the requirements directory -# - CI/CD environments where pkgs.zip might be extracted to a directory -_pkgs_zip_valid = False -if pkgs_zip_path.exists(): - # Check if it's a file (not a directory) - in some CI environments, - # pkgs.zip might be extracted to a directory instead of kept as a ZIP. - if pkgs_zip_path.is_file(): - # Validate that it's actually a valid ZIP file before adding to sys.path - try: - with zipfile.ZipFile(pkgs_zip_path, "r") as zf: - # Quick validation - just check that we can read the ZIP directory - zf.namelist() - _pkgs_zip_valid = True - except (zipfile.BadZipFile, OSError, IOError) as e: - # Not a valid ZIP file or can't be read - skip loading from pkgs.zip - warnings.warn( - f"Failed to load packages from {pkgs_zip_path}: {e}. " - "Third-party dependencies will be loaded from the Python environment instead.", - RuntimeWarning, - stacklevel=2, - ) - -# If pkgs.zip is not found, assume pip-style installation where dependencies -# are installed directly in the Python environment. In this case, we still -# install the import hook to enable tank_vendor.* aliasing for compatibility. -if not _pkgs_zip_valid: - # Install import hook even without pkgs.zip for pip installations - _install_import_hook() -else: - # Add pkgs.zip to sys.path so Python can import packages directly from the ZIP. - # Insert at position 0 to prioritize over other installed packages. - sys.path.insert(0, str(pkgs_zip_path)) try: - # Step 1: Auto-discover all top-level packages in pkgs.zip import importlib - with zipfile.ZipFile(pkgs_zip_path, "r") as zf: - # Get all top-level package names from the ZIP - top_level_packages = set() - for name in zf.namelist(): - # Extract first component of path (top-level package/module) - parts = name.split("/") - if parts[0] and not parts[0].endswith(".py"): - # It's a package directory - top_level_packages.add(parts[0]) - elif parts[0].endswith(".py") and parts[0] != "__pycache__": - # It's a top-level module file - top_level_packages.add(parts[0][:-3]) # Remove .py - - # Filter out non-importable items: - # - .dist-info: Package metadata directories - # - __pycache__: Python bytecode cache - # - .py: Single file modules (already captured as packages) - # - .pyd/.so/.dylib: Platform-specific binary extensions - # - _*: Private/internal modules (e.g., _ruamel_yaml.cp311-win_amd64.pyd) - top_level_packages = { - pkg - for pkg in top_level_packages - if not pkg.endswith(".dist-info") - and pkg != "__pycache__" - and not pkg.endswith(".py") - and not pkg.endswith(".pyd") # Windows binary modules - and not pkg.endswith(".so") # Unix/Linux binary modules - and not pkg.endswith(".dylib") # macOS binary modules - and not pkg.startswith("_") # Private/internal modules - } - - # Step 2: Import and register each top-level package under tank_vendor namespace + # Step 3: Auto-discover all top-level packages in the zip. + top_level_packages = _discover_top_level_packages(zip_path) + + # Step 4: Import and register each top-level package under the + # tank_vendor namespace. for package_name in sorted(top_level_packages): + # Collision check: an earlier zip already claimed this name. + # Earlier zips win (pkgs.zip is loaded before requirements/any/). + if f"tank_vendor.{package_name}" in sys.modules: + warnings.warn( + f"Skipping {package_name} from {zip_path}: " + f"already registered under tank_vendor.{package_name} " + f"from an earlier zip.", + RuntimeWarning, + ) + continue + try: + # Import the real module and alias it under tank_vendor.* in + # sys.modules; also expose it as an attribute on this package + # so `from tank_vendor import ` works without going + # through the meta path finder. + # Import the package mod = importlib.import_module(package_name) # Register in sys.modules under tank_vendor namespace sys.modules[f"tank_vendor.{package_name}"] = mod - - # Also set as attribute on tank_vendor module for direct access globals()[package_name] = mod - - except ImportError as e: - # Some packages might not import cleanly on all platforms - # Log but don't fail - they might not be needed - warnings.warn(f"Could not import {package_name} from pkgs.zip: {e}") - - # Step 3: Install import hook for lazy submodule loading - # This enables imports like: from tank_vendor.shotgun_api3.lib import httplib2 - # without pre-importing all submodules (which can fail on version incompatibilities) - _install_import_hook() - - # Step 4: Apply package-specific patches - # These patches work around limitations or fix issues with specific packages - if "shotgun_api3" in sys.modules: - _patch_shotgun_api3_certs(pkgs_zip_path) + except Exception as e: + # Per-package import failures are tolerated. The catch is + # intentionally broad: a future shared vendor using syntax + # newer than the current Python (e.g. PEP 604 union syntax + # `int | None`) would raise SyntaxError at parse time, not + # ImportError. flow_data_sdk on Python 3.9 raises ImportError + # for its references to types.UnionType / typing.TypeAlias, + # which this catch also handles. Wholesale loader failures + # are still handled by the outer try/except. + warnings.warn( + f"Could not import {package_name} from {zip_path}: {e}", + RuntimeWarning, + stacklevel=2, + ) except Exception as e: - # Clean up sys.path on failure to avoid leaving it in an inconsistent state - # with a non-functional ZIP path that could interfere with subsequent imports - sys.path.remove(str(pkgs_zip_path)) + # Clean up sys.path on a wholesale failure so we don't leave a + # non-functional zip on the path interfering with other imports. + try: + sys.path.remove(str(zip_path)) + except ValueError: + pass raise RuntimeError( - f"Failed to import required modules from {pkgs_zip_path}: {e}" + f"Failed to import required modules from {zip_path}: {e}" ) from e + + return True + + +def _release_importlib_metadata_handles(): + """ + Release file handles that importlib.metadata holds on vendor zips. + + Windows-only workaround. + + importlib.metadata.FastPath.__new__ is @lru_cache'd, so the FastPath + instance for any zip it probes is kept alive forever. Inside + FastPath.zip_children(), the line `self.joinpath = zip_path.joinpath` + binds the zipfile.Path (and its underlying open ZipFile) as an instance + attribute on the cached FastPath — so the file handle stays open for + the lifetime of the cache. + + This bites us on Windows / Python 3.13 when flow_data_sdk's _version.py + runs importlib.metadata.version("flow-data-sdk") during import. The + cached FastPath keeps our shared zip open, which then prevents the + tank share_core command from moving install/core (WinError 32 sharing + violation). + + Linux and macOS don't have Windows' sharing-violation semantics — moving + or deleting files with open handles is allowed — so this cleanup is a + no-op on those platforms (and was observed to break a Linux/3.13 + integration test, so we gate strictly on win32). + + invalidate_caches() calls FastPath.__new__.cache_clear() which drops + the FastPath references. gc.collect() forces __del__ on the underlying + ZipFile objects so the handles close immediately rather than at the + next garbage collection cycle. + """ + if sys.platform != "win32": + return + from importlib.metadata import MetadataPathFinder + # invalidate_caches() is declared as `def invalidate_caches(cls)` without + # @classmethod in some Python versions, so call it on an instance for + # cross-version compatibility. + MetadataPathFinder().invalidate_caches() + import gc + + gc.collect() + + +# ============================================================================ +# MAIN INITIALIZATION +# ============================================================================ + +_requirements_dir = pathlib.Path(__file__).resolve().parent.parent.parent / "requirements" + +# Load order matters for two distinct reasons: +# +# 1. sys.modules registration: the FIRST zip to register a top-level package +# wins (later zips' duplicates are skipped). So pkgs.zip is loaded first +# to keep its version-pinned dependencies authoritative. +# +# 2. sys.path order: we insert each zip at sys.path[0], so the LAST zip +# loaded ends up at the front. We want shared zips ahead of pkgs.zip on +# sys.path so that importlib.metadata.version() lookups (e.g. flow_data_sdk's +# _version.py querying its own dist-info) short-circuit on the shared zip +# and never scan pkgs.zip. Scanning pkgs.zip via importlib.metadata caches +# a FastPath instance that holds an open zipfile, which on Windows +# prevents the tank share_core command from moving install/core. +# +# So: load pkgs.zip first (sys.modules), then shared zips (sys.path front). +_pkgs_zip_path = ( + _requirements_dir + / f"{sys.version_info.major}.{sys.version_info.minor}" + / "pkgs.zip" +) +_pkgs_loaded = _load_packages_from_zip(_pkgs_zip_path) +if _pkgs_loaded and "shotgun_api3" in sys.modules: + _patch_shotgun_api3_certs(_pkgs_zip_path) + +# Shared zips (optional, Python-version-independent). Drop a *.zip into +# requirements/any/ and it will be loaded automatically. Shared vendors are +# expected to use the system trust store and not ship data files that would +# need extraction from inside the zip. +_shared_dir = _requirements_dir / "any" +if not _shared_dir.is_dir(): + # Fallback for pip-installed packages: setup.py's build_py copies + # requirements/any/*.zip into tank_vendor/vendor_any/ in the wheel. + _shared_dir = pathlib.Path(__file__).resolve().parent / "vendor_any" +if _shared_dir.is_dir(): + for _shared_zip in sorted(_shared_dir.glob("*.zip")): + _load_packages_from_zip(_shared_zip) + +# Install the lazy import hook for nested submodule access. +# Idempotent via the _tank_vendor_meta_finder guard, so calling it once +# after both load steps is safe and sufficient. +_install_import_hook() + +# Windows-only cleanup: drop importlib.metadata's cached file handles on our +# vendor zips so the tank share_core command can move install/core without +# hitting WinError 32 sharing violations. No-op on Linux/macOS. +_release_importlib_metadata_handles() diff --git a/python/tk-core/python/tank_vendor/adsk_auth/__init__.py b/python/tk-core/python/tank_vendor/adsk_auth/__init__.py new file mode 100644 index 00000000..5f8dcd21 --- /dev/null +++ b/python/tk-core/python/tank_vendor/adsk_auth/__init__.py @@ -0,0 +1,17 @@ +# Copyright (c) 2025 Shotgun Software Inc. +# CONFIDENTIAL AND PROPRIETARY + +""" +Adsk auth – minimal Autodesk Platform Services (APS) authentication using PKCE. + +Single flow: file store -> refresh token -> browser PKCE. No Identity Client. +""" + +from .config import AuthConfig +from .token import clear_stored_tokens, get_access_token + +__all__ = [ + "AuthConfig", + "get_access_token", + "clear_stored_tokens", +] diff --git a/python/tk-core/python/tank_vendor/adsk_auth/config.py b/python/tk-core/python/tank_vendor/adsk_auth/config.py new file mode 100644 index 00000000..31668bf1 --- /dev/null +++ b/python/tk-core/python/tank_vendor/adsk_auth/config.py @@ -0,0 +1,52 @@ +# Copyright (c) 2025 Shotgun Software Inc. +# CONFIDENTIAL AND PROPRIETARY + +"""APS PKCE configuration.""" + +from __future__ import annotations + +from dataclasses import dataclass +from typing import List + + +@dataclass +class AuthConfig: + """Configuration for APS PKCE authentication.""" + + application_id: str + base_url: str + callback_url: str + required_application_scopes: List[str] + storage_dir: str + description: str = "" + + def __post_init__(self) -> None: + if not self.application_id or not self.application_id.strip(): + raise ValueError("application_id is required") + if not self.base_url or not self.base_url.strip(): + raise ValueError("base_url is required") + if not self.callback_url or not self.callback_url.strip(): + raise ValueError("callback_url is required") + if not self.required_application_scopes: + raise ValueError("required_application_scopes must not be empty") + if not self.storage_dir or not self.storage_dir.strip(): + raise ValueError("storage_dir is required") + self.base_url = _normalize_base_url(self.base_url.strip()) + self.application_id = self.application_id.strip() + self.callback_url = self.callback_url.strip() + + +def _normalize_base_url(base_url: str) -> str: + """Normalize base URL for APS (scheme + netloc).""" + from urllib.parse import urlsplit + + scheme, netloc, path, _, _ = urlsplit(base_url) + if scheme == "" and netloc == "" and path: + if path.startswith("localhost") or path.startswith("localhost/"): + port = path.split("/")[0].split(":")[-1] if ":" in path else "" + return f"http://localhost:{port}" if port else "http://localhost" + first = path.split("/")[0] + return f"https://{first}" + if scheme in ("http", "https"): + return f"{scheme}://{netloc}" + raise ValueError(f"base_url must use http or https, got scheme={scheme!r}") diff --git a/python/tk-core/python/tank_vendor/adsk_auth/file_store.py b/python/tk-core/python/tank_vendor/adsk_auth/file_store.py new file mode 100644 index 00000000..09185df8 --- /dev/null +++ b/python/tk-core/python/tank_vendor/adsk_auth/file_store.py @@ -0,0 +1,114 @@ +# Copyright (c) 2025 Shotgun Software Inc. +# CONFIDENTIAL AND PROPRIETARY + +"""Token storage via a local JSON file (replaces keyring dependency).""" + +from __future__ import annotations + +import getpass +import json +import logging +import os +import sys +from typing import Any, Dict + +_logger = logging.getLogger(__name__) + +SERVICE_PREFIX = "adsk.flow" +TOKEN_TYPES = ("access_token", "refresh_token") + +_TOKEN_FILE_NAME = "adsk_flow_tokens.json" + + +def _service_name(application_id: str, token_type: str) -> str: + return f"{SERVICE_PREFIX}.{application_id}.{token_type}" + + +def _token_file_path(storage_dir: str) -> str: + return os.path.join(storage_dir, _TOKEN_FILE_NAME) + + +def _load(storage_dir: str) -> Dict[str, Any]: + path = _token_file_path(storage_dir) + if not os.path.exists(path): + return {} + try: + with open(path, "r") as fh: + data = json.load(fh) + return data if isinstance(data, dict) else {} + except Exception: + _logger.debug("Could not read token file %s", path, exc_info=True) + return {} + + +def _save(storage_dir: str, data: Dict[str, Any]) -> None: + if not os.path.exists(storage_dir): + old_umask = os.umask(0o077) + try: + os.makedirs(storage_dir, 0o700) + finally: + os.umask(old_umask) + + path = _token_file_path(storage_dir) + old_umask = os.umask(0o177) + try: + with open(path, "w") as fh: + json.dump(data, fh) + finally: + os.umask(old_umask) + + # Belt-and-suspenders: explicitly set permissions on POSIX. + if sys.platform != "win32": + try: + os.chmod(path, 0o600) + except OSError: + pass + + +def get_access_token( + storage_dir: str, application_id: str, profile: str +) -> str | None: + """Read access token from the file store.""" + data = _load(storage_dir) + return data.get(_service_name(application_id, "access_token"), {}).get(profile) + + +def get_refresh_token( + storage_dir: str, application_id: str, profile: str +) -> str | None: + """Read refresh token from the file store.""" + data = _load(storage_dir) + return data.get(_service_name(application_id, "refresh_token"), {}).get(profile) + + +def persist_tokens( + storage_dir: str, + application_id: str, + profile: str, + tokens: Dict[str, Any], +) -> None: + """Store token dict (access_token, refresh_token) in the file store.""" + data = _load(storage_dir) + for token_type in TOKEN_TYPES: + if tokens.get(token_type): + service = _service_name(application_id, token_type) + data.setdefault(service, {})[profile] = tokens[token_type] + _save(storage_dir, data) + + +def delete_tokens(storage_dir: str, application_id: str, profile: str) -> None: + """Remove all stored tokens for this app and profile.""" + data = _load(storage_dir) + changed = False + for token_type in TOKEN_TYPES: + service = _service_name(application_id, token_type) + if service in data and profile in data[service]: + del data[service][profile] + changed = True + if changed: + _save(storage_dir, data) + + +def get_user_profile(profile: str | None) -> str: + """Return profile (username) for the store; default current OS user.""" + return profile or getpass.getuser() diff --git a/python/tk-core/python/tank_vendor/adsk_auth/pkce.py b/python/tk-core/python/tank_vendor/adsk_auth/pkce.py new file mode 100644 index 00000000..a1fa57fa --- /dev/null +++ b/python/tk-core/python/tank_vendor/adsk_auth/pkce.py @@ -0,0 +1,292 @@ +# Copyright (c) 2025 Shotgun Software Inc. +# CONFIDENTIAL AND PROPRIETARY + +"""PKCE flow: code pair, auth URL, callback server, code/refresh exchange.""" + +from __future__ import annotations + +import base64 +import errno +import hashlib +import json +import logging +import secrets +import socket +import threading +import urllib.request +import webbrowser +from http.server import BaseHTTPRequestHandler, HTTPServer +from socketserver import ThreadingMixIn +from typing import Any, Dict +from urllib.parse import parse_qs, urlencode, urlparse + +from .config import AuthConfig + +_logger = logging.getLogger(__name__) + +REST_TIMEOUT = 30 + + +def create_code_pair() -> tuple[str, str]: + """Create PKCE code_verifier and code_challenge (S256).""" + code_verifier = secrets.token_urlsafe(40) + digest = hashlib.sha256(code_verifier.encode("utf-8")).digest() + code_challenge = base64.urlsafe_b64encode(digest).decode("utf-8").rstrip("=") + return (code_challenge, code_verifier) + + +def build_authorize_url(config: AuthConfig, code_challenge: str) -> tuple[str, str]: + """Build authorize URL and state; returns (url, state).""" + state = secrets.token_urlsafe() + params = { + "client_id": config.application_id, + "redirect_uri": config.callback_url, + "response_type": "code", + "scope": " ".join(config.required_application_scopes), + "state": state, + "code_challenge_method": "S256", + "code_challenge": code_challenge, + "nonce": secrets.token_urlsafe(), + } + url = f"{config.base_url}/authentication/v2/authorize?{urlencode(params)}" + return (url, state) + + +def exchange_code( + config: AuthConfig, + code: str, + code_verifier: str, +) -> Dict[str, Any]: + """Exchange authorization code for tokens.""" + data = { + "code": code, + "grant_type": "authorization_code", + "redirect_uri": config.callback_url, + "client_id": config.application_id, + "code_verifier": code_verifier, + } + encoded_data = urlencode(data).encode("utf-8") + req = urllib.request.Request( + f"{config.base_url}/authentication/v2/token", + data=encoded_data, + headers={"Content-Type": "application/x-www-form-urlencoded", "Accept": "application/json"}, + method="POST", + ) + with urllib.request.urlopen(req, timeout=REST_TIMEOUT) as response: + return json.loads(response.read()) + + +def exchange_refresh_token(config: AuthConfig, refresh_token: str) -> Dict[str, Any]: + """Exchange refresh token for new access (and optionally refresh) token.""" + data = { + "grant_type": "refresh_token", + "refresh_token": refresh_token, + "client_id": config.application_id, + } + encoded_data = urlencode(data).encode("utf-8") + req = urllib.request.Request( + f"{config.base_url}/authentication/v2/token", + data=encoded_data, + headers={"Content-Type": "application/x-www-form-urlencoded"}, + method="POST", + ) + with urllib.request.urlopen(req, timeout=20) as response: + return json.loads(response.read()) + + +def _callback_server_port(callback_url: str) -> int: + parsed = urlparse(callback_url) + if parsed.port is not None: + return parsed.port + return 80 if parsed.scheme == "http" else 443 + + +# Errno for "address family not supported" (IPv6 disabled or unavailable). +# Unix: EAFNOSUPPORT (97); Windows: WSAEAFNOSUPPORT (10047). +_ERRNO_AF_NOT_SUPPORTED = (getattr(errno, "EAFNOSUPPORT", 97), 10047) + + +def _is_port_in_use(port: int) -> bool: + """Return True if the loopback port is already bound (IPv4 or IPv6).""" + port = int(port) + try: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind(("127.0.0.1", port)) + except OSError as e: + if getattr(e, "errno", None) in (errno.EADDRINUSE, errno.EACCES): + return True + raise + # If IPv6 is not available (EAFNOSUPPORT etc.), skip probe and assume port is free for our use. + try: + with socket.socket(socket.AF_INET6, socket.SOCK_STREAM) as s: + s.bind(("::1", port)) + except OSError as e: + err = getattr(e, "errno", None) + if err in (errno.EADDRINUSE, errno.EACCES): + return True + if err in _ERRNO_AF_NOT_SUPPORTED: + _logger.debug("IPv6 not available, skipping IPv6 port probe") + return False + raise + return False + + +# Simple page with button (no immediate window.close) so callback is fully +# processed before the window closes. +_CALLBACK_HTML = b""" +

Authentication successful. You can close this window.

+ +""" + + +class _CallbackHandler(BaseHTTPRequestHandler): + """Capture OAuth callback ?code=...&state=... from the local HTTP server.""" + + session_store: Dict[str, Any] = {} + + def do_GET(self) -> None: + q = parse_qs(urlparse(self.path).query) + if "error" in q: + msg = q.get("error_description", q.get("error", [b"Unknown error"])) + _logger.error("OAuth callback error: %s", msg[0] if msg else "unknown") + elif "code" in q and "state" in q: + self.session_store[q["state"][0]] = q["code"][0] + _logger.info("Received OAuth callback with code and state") + self.send_response(200) + self.end_headers() + self.wfile.write(_CALLBACK_HTML) + # Do NOT call server.shutdown() here: with ThreadingMixIn the handler runs + # in a worker thread; the main server thread is blocked in accept(). Rely + # on the main thread polling session_store and timing out instead. + + def log_message(self, format: str, *args: Any) -> None: + _logger.debug("%s", args) + + +class _ThreadingCallbackServerDualStack(ThreadingMixIn, HTTPServer): + """Threaded HTTP server binding to ::1 (IPv6 loopback only).""" + + address_family = socket.AF_INET6 + + def server_bind(self) -> None: + self.socket = socket.socket(self.address_family, socket.SOCK_STREAM) + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.socket.bind(("::1", self.server_address[1])) + self.server_address = self.socket.getsockname() + + +class _ThreadingCallbackServerIPv4(ThreadingMixIn, HTTPServer): + """Threaded HTTP server binding to 127.0.0.1 (IPv4 loopback only).""" + + def server_bind(self) -> None: + self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.socket.bind(("127.0.0.1", self.server_address[1])) + self.server_address = self.socket.getsockname() + + +def run_callback_server( + session_store: Dict[str, Any], + port: int, + ready_event: threading.Event | None = None, + init_error: list[BaseException] | None = None, + server_container: list | None = None, +) -> None: + """Run HTTP server in current thread to capture ?code=...&state=... .""" + _CallbackHandler.session_store = session_store + port = int(port) + try: + server = _ThreadingCallbackServerDualStack(("::1", port), _CallbackHandler) + except OSError as e: + err = getattr(e, "errno", None) + if err in (errno.EADDRINUSE, errno.EACCES): + if init_error is not None: + init_error.append(e) + if ready_event is not None: + ready_event.set() + return + if err in _ERRNO_AF_NOT_SUPPORTED: + _logger.debug("IPv6 not available, using IPv4 loopback callback server") + server = _ThreadingCallbackServerIPv4(("127.0.0.1", port), _CallbackHandler) + else: + raise + # Request handler threads must be daemon so the process exits after we have the code. + server.daemon_threads = True + if server_container is not None: + server_container.append(server) + if ready_event is not None: + ready_event.set() + server.serve_forever() + + +def web_authenticate( + config: AuthConfig, + *, + time_out: float = 30.0, + browser: Any = None, +) -> dict[str, Any]: + """Run PKCE in browser; return token dict (access_token, refresh_token, ...).""" + import time + + code_challenge, code_verifier = create_code_pair() + auth_url, state = build_authorize_url(config, code_challenge) + session_store: Dict[str, Any] = {state: None} + port = _callback_server_port(config.callback_url) + if _is_port_in_use(port): + raise RuntimeError( + f"Port {port} is already in use (callback URL {config.callback_url}). " + "Another auth process may be running. Exit it or use a different callback URL." + ) + ready_event = threading.Event() + init_error: list[BaseException] = [] + server_container: list = [] + + server_thread = threading.Thread( + target=run_callback_server, + args=(session_store, port), + kwargs={ + "ready_event": ready_event, + "init_error": init_error, + "server_container": server_container, + }, + ) + server_thread.daemon = True + server_thread.start() + # Ensure server is bound and listening before opening browser (avoids race on Windows) + if not ready_event.wait(timeout=5): + _logger.warning("Callback server may not be ready yet") + if init_error: + e = init_error[0] + raise RuntimeError( + f"Port {port} is already in use (callback URL {config.callback_url}). " + "Another auth process may be running. Exit it or use a different callback URL." + ) from e + time.sleep(0.5) + try: + try: + b = webbrowser.get(using=browser) + b.open_new(auth_url) + # With ThreadingMixIn the server thread never exits; poll with short joins + # so we return as soon as the callback handler has set session_store[state]. + deadline = time.monotonic() + time_out + while time.monotonic() < deadline: + if session_store.get(state) is not None: + break + server_thread.join(timeout=0.25) + except webbrowser.Error as e: + _logger.error("Browser error: %s", e) + server_thread.join(timeout=0.5) + finally: + if server_container: + server_container[0].shutdown() + server_container[0].server_close() + + if session_store.get(state) is None: + raise RuntimeError("Failed to obtain authorization code from browser") + code = session_store[state] + token = exchange_code(config, code, code_verifier) + if not token or "access_token" not in token: + raise RuntimeError("Token exchange did not return an access_token") + return token diff --git a/python/tk-core/python/tank_vendor/adsk_auth/token.py b/python/tk-core/python/tank_vendor/adsk_auth/token.py new file mode 100644 index 00000000..2442e2a9 --- /dev/null +++ b/python/tk-core/python/tank_vendor/adsk_auth/token.py @@ -0,0 +1,112 @@ +# Copyright (c) 2025 Shotgun Software Inc. +# CONFIDENTIAL AND PROPRIETARY + +"""Get access token: file store -> refresh -> browser PKCE.""" + +from __future__ import annotations + +import logging +from typing import Any, Optional +from urllib.error import HTTPError + +try: + import jwt +except ImportError: + # TODO: Remove when Python 3.7 is discontinued + jwt = None + +from .config import AuthConfig +from .file_store import delete_tokens +from .file_store import get_access_token as get_access_token_from_store +from .file_store import get_refresh_token, get_user_profile, persist_tokens +from .pkce import exchange_refresh_token, web_authenticate + +_logger = logging.getLogger(__name__) + +# In-memory cache: profile -> access_token (avoids file read every call) +_access_token_cache: dict[str, str] = {} + + +def get_access_token( + config: AuthConfig, + *, + profile: Optional[str] = None, + force_refresh: bool = False, + force_reauthentication: bool = False, + time_out: float = 30.0, + browser: Any = None, +) -> str: + """ + Return a valid access token: use cache, then file store, then refresh, then browser PKCE. + + Raises: + RuntimeError: If a token could not be obtained. + """ + global _access_token_cache + user_profile = get_user_profile(profile) + + if force_reauthentication or force_refresh: + _access_token_cache.pop(user_profile, None) + + # 1. Valid token in cache? + cached = _access_token_cache.get(user_profile) + if cached and not (force_refresh or force_reauthentication): + if jwt is None: # TODO: Remove when Python 3.7 is discontinued + return cached + try: + jwt.decode(cached, options={"verify_signature": False, "verify_exp": True}) + return cached + except (jwt.ExpiredSignatureError, jwt.DecodeError): + pass + + if force_reauthentication: + delete_tokens(config.storage_dir, config.application_id, user_profile) + + # 2. Valid token in file store? + if not (force_reauthentication or force_refresh): + try: + access_token = get_access_token_from_store( + config.storage_dir, config.application_id, user_profile + ) + if access_token: + if jwt is None: # TODO: Remove when Python 3.7 is discontinued + _access_token_cache[user_profile] = access_token + return access_token + jwt.decode( + access_token, + options={"verify_signature": False, "verify_exp": True}, + ) + _access_token_cache[user_profile] = access_token + return access_token + except (jwt.ExpiredSignatureError, jwt.DecodeError): + pass + + # 3. Refresh token? + try: + refresh_token = get_refresh_token( + config.storage_dir, config.application_id, user_profile + ) + if refresh_token: + _logger.debug("Using refresh token") + token_dict = exchange_refresh_token(config, refresh_token) + persist_tokens(config.storage_dir, config.application_id, user_profile, token_dict) + access_token = token_dict["access_token"] + _access_token_cache[user_profile] = access_token + return access_token + except (RuntimeError, HTTPError) as e: + _logger.debug("Refresh failed: %s", e) + + # 4. Browser PKCE + _logger.warning("Opening browser to authenticate (timeout %.1fs)", time_out) + token_dict = web_authenticate(config, time_out=time_out, browser=browser) + persist_tokens(config.storage_dir, config.application_id, user_profile, token_dict) + access_token = token_dict["access_token"] + _access_token_cache[user_profile] = access_token + return access_token + + +def clear_stored_tokens(config: AuthConfig, profile: Optional[str] = None) -> None: + """Remove tokens from file store and in-memory cache for this app/profile.""" + user_profile = get_user_profile(profile) + _access_token_cache.pop(user_profile, None) + delete_tokens(config.storage_dir, config.application_id, user_profile) diff --git a/python/tk-core/python/tank_vendor/flow_integration_sdk/__init__.py b/python/tk-core/python/tank_vendor/flow_integration_sdk/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/python/tk-core/python/tank_vendor/flow_integration_sdk/dependency.py b/python/tk-core/python/tank_vendor/flow_integration_sdk/dependency.py new file mode 100644 index 00000000..3ecca656 --- /dev/null +++ b/python/tk-core/python/tank_vendor/flow_integration_sdk/dependency.py @@ -0,0 +1,467 @@ +# - +# ***************************************************************************** +# Copyright 2026 Autodesk, Inc. All rights reserved. +# +# These coded instructions, statements, and computer programs contain +# unpublished proprietary information written by Autodesk, Inc. and are +# protected by Federal copyright law. They may not be disclosed to third +# parties or copied or duplicated in any form, in whole or in part, without +# the prior written consent of Autodesk, Inc. +# ***************************************************************************** +# + + +""" +This module contains convenience classes/utilities for dealing with +file dependencies within an asset. +""" +from __future__ import annotations # needed for python 3.9 support + +import json +import re +from dataclasses import dataclass, field, fields +from enum import Enum + +from tank_vendor.flow_integration_sdk.exceptions import FlowError +from tank_vendor.flow_integration_sdk.storage import storage_key_to_asset_id +from tank_vendor.flow_integration_sdk.objects import ( + FlowRevision, + FlowVersion, +) +from tank_vendor.flow_integration_sdk.utils import ( + cleanpath, + to_regex_safe_wildcard_string, + trace, +) + + +class DepType(Enum): + """Enum of dependency data types.""" + + #: Link to another asset in Flow. + ASSET = "asset" + #: Link to a file external to Flow, or packaged within current asset. + LOCAL = "local" + #: No specific type. + NONE = "" + #: Link to the main scene file of the current asset. + ROOT = "root" + + +@dataclass +class DependencyData: + """Store data relevant to an dependency within a scene. + + General Properties: + + - dep_type: General type of dependency. See :class:`.DepType` enum for valid values. + - node_handle: Unique identifier to the node with the dependency. + - node_type: Type name of node (specific type of dependency). + - attribute: Name of attribute which stores the file path (not always applicable). + - file_path: Absolute file path of dependency. + - raw_path: Raw file path stored in scene. + - dependencies: List of nested dependencies within the file designated by file_path. + + Asset Properties: (pertinent to asset type dependencies only) + + - asset_id: Asset entity id. + - version: Version of revision. + - revision_id: Revision id. + - component_name: Unique component identifier within revision. + - blob_index: Index into blob array of component. + """ + + # General properties + + dep_type: DepType = DepType.NONE + node_handle: str = "" + node_type: str = "" + attribute: str = "" + file_path: str = "" + raw_path: str = "" + parent: DependencyData | None = None + dependencies: list[DependencyData] = field(default_factory=lambda: []) + + # Asset properties + asset_id: str = "" + revision_id: str = "" + version_id: str = "" + component_name: str = "" + blob_index: int = 0 + + def identify_component(self): + """Determine asset component identification based on file path. + If provided, copy info from cached dependency tree to avoid querying it. + """ + # Parse file path for asset info + comp_info = identify_component(self.file_path) + if comp_info: + self.asset_id = comp_info.get("asset_id") + self.revision_id = comp_info.get("revision_id") + self.version_id = comp_info.get("version_id") + self.component_name = comp_info.get("component_name") + self.blob_index = comp_info.get("blob_index") + + @property + def revision_num(self): + """Return number of revision based on revision id.""" + return FlowRevision.get_revision_number(self.revision_id) + + @property + def version_num(self): + """Return number of version based on version id.""" + return FlowVersion.get_version_number(self.version_id) + + def set_type(self): + """Set dependency type based on current properties.""" + # Can assume any dependency with asset information is an asset dependency + self.dep_type = DepType.ASSET if self.asset_id else DepType.LOCAL + + def flatten_dependencies(self): + """Create flat list of all dependencies in tree.""" + deps = [self] + for dep in self.dependencies: + deps.extend(dep.flatten_dependencies()) + return deps + + def get_external_dependencies(self, top_level: bool = True) -> list[DependencyData]: + """Return list of external dependency objects. + + .. note:: external dependencies of internal dependencies will be ignored + because they are not considered external to the current asset context. + + Args: + top_level: If True, only return external dependencies in immediate + dependency list. Otherwise, include sub dependencies too. + + Returns + List of DependencyData objects of type DepType.LOCAL. + """ + ext_deps = [] + for dep in self.dependencies: + if dep.dep_type == DepType.LOCAL: + ext_deps.append(dep) + if not top_level and dep.dep_type != DepType.ASSET: + ext_deps.extend(dep.get_external_dependencies(top_level=False)) + return ext_deps + + def get_internal_dependencies(self, top_level: bool = True) -> list[DependencyData]: + """Return list of internal dependency objects. + + Args: + top_level: If True, only return internal dependencies in immediate + dependency list. Otherwise, include all sub dependencies too. + + Returns: + List of DependencyData objects of type DepType.ASSET. + """ + int_deps = [] + for dep in self.dependencies: + # In searching for internal dependencies, we must also include those + # that are referenced by external dependencies because they also + # need to be "registered" as internal references of current asset. + # + # If only getting top level internal references, do not search sub + # dependencies once an internal dependency is found, but must continue + # to search sub dependencies of external dependencies in case an internal + # reference exists within. + if dep.dep_type == DepType.ASSET: + int_deps.append(dep) + if top_level: + continue + int_deps.extend(dep.get_internal_dependencies(top_level=top_level)) + + return int_deps + + def find( + self, + node_handle: str | None = None, + file_path: str | None = None, + node_type: str | None = None, + ) -> list[DependencyData]: + """Find node within dependency tree. + + Note: multiple criteria acts as a union, not an intersection. + + Args: + node_handle: If provided, match dependency node with this handle. + Wildcard character '*' is supported. + This criteria will be given precedence. + file_path: If provided, match by file path. + Wildcard character '*' is supported. + This criteria has second priority. + node_type: If provided, match by node type. + Wildcard character '*' is supported. + This criteria has third priority. + + Returns: + List of DepedencyData nodes that match to ANY of given criteria. + """ + matches = [] + matched_self = False + if node_handle is not None: + expression = f"^{to_regex_safe_wildcard_string(node_handle)}$" + if re.match(expression, self.node_handle): + matches.append(self) + matched_self = True + if not matched_self and file_path is not None: + expression = f"{to_regex_safe_wildcard_string(file_path.lower())}$" + if re.match(expression, self.file_path.lower()): + matches.append(self) + matched_self = True + if not matched_self and node_type is not None: + expression = f"{to_regex_safe_wildcard_string(node_type)}$" + if re.match(expression, self.node_type): + matches.append(self) + for dep in self.dependencies: + result = dep.find(node_handle, file_path, node_type) + matches.extend(result) + + return matches + + def contains(self, dep: DependencyData) -> DependencyData | None: + """Check tree for equivalent dependency node and return.""" + deps = self.flatten_dependencies() + for d in deps: + if d == dep: + return d + return None + + def match(self, dep: DependencyData) -> DependencyData | None: + """Find matching dependency node in tree. + + A match entails that the following properties are the same: + * node_handle + * node_type + * attribute + * asset_id + * component_name + + Args: + dep: The dependency data to be matched. + + Returns: + First node in the tree that fits matching criteria, or None if not found. + """ + deps = self.flatten_dependencies() + for d in deps: + if d.node_handle != dep.node_handle: + continue + elif d.node_type != dep.node_type: + continue + elif d.attribute != dep.attribute: + continue + elif d.asset_id != dep.asset_id: + continue + elif d.component_name != dep.component_name: + continue + return d + return None + + def __lt__(self, other: DependencyData) -> bool: + """Implement less-than comparison operator to make objects sortable.""" + return self.node_handle < other.node_handle + + def __eq__(self, other: DependencyData) -> bool: + """Implement equals operator.""" + if ( + self.node_handle == other.node_handle + and self.node_type == other.node_type + and self.attribute == other.attribute + and self.file_path == other.file_path + ): + return True + return False + + def asdict(self): + """Convert object to dictionary.""" + # NOTE: using default asdict() function causes infinite recursion + + # Convert basic properties + result = {} + for f in fields(self): + if f.name in ["dependencies", "parent"]: + continue + result[f.name] = getattr(self, f.name) + + # Convert dependency list recursively + result["dependencies"] = [dep.asdict() for dep in self.dependencies] + + # Convert dep_type to string + result["dep_type"] = self.dep_type.value + + return result + + def pprint(self, index: int = 0, tabs: int = 0, recursive=True): + """Print dependency tree in a readable way.""" + print("\t" * tabs + "-----------------------------------------") + print("\t" * tabs + f"{index} - {self.dep_type.name}") + print("\t" * tabs + "-----------------------------------------") + + asset_props = [ + "asset_id", + "version", + "revision_id", + "component_name", + "blob_index", + ] + + for prop, value in self.__dict__.items(): + if prop in ["dependencies", "dep_type"]: + continue + elif prop == "parent": + if value is not None: + print("\t" * tabs + f"{prop}: {value.node_handle}") + continue + elif prop in asset_props and self.dep_type is not DepType.ASSET: + continue + print("\t" * tabs + f"{prop}: {str(value)}") + + if recursive and self.dependencies: + print(" ") + print("\t" * tabs + "Sub-dependencies:") + for i, dep in enumerate(self.dependencies): + dep.pprint(i, tabs + 1) + + @classmethod + def convert(cls, dep_json: dict | str) -> DependencyData: + """Convert a json representation of a dependency tree into object representation. + + Args: + dep_json: Json in dictionary or string format. + + Returns: + Root of dependency tree. + + Raises: + ValueError: If dep_json is a string that cannot be parsed as JSON. + """ + # Convert json string to dictionary + if isinstance(dep_json, str): + try: + dep_json = json.loads(dep_json) + except Exception as exc: # pylint: disable=broad-except + raise ValueError("Invalid json provided.") from exc + + dep = DependencyData(**dep_json) # type: ignore[arg-type] + + try: + # dep type is assumed to be a string, convert back to enum value + dep.dep_type = DepType(dep.dep_type) + except Exception: + dep.dep_type = DepType.NONE + + # Properly convert sub-dependencies to objects + subdeps = [] + for d in dep.dependencies: + subdeps.append(DependencyData.convert(d)) # type: ignore[arg-type] + dep.dependencies = subdeps + for d in subdeps: + d.parent = dep + + return dep + + +@trace +def identify_component(file_path: str) -> dict | None: + """Given a file path, determine if it belongs to an asset, and + return information identifying the exact component blob that the path + is associated with. + + NOTE: Only paths within primary storage (NFS cache) can be identified. + All other paths, including sandbox paths, will return None. + + Args: + file_path: Absolute path to a file. + ignore_root: If True, root directory does not need to map to + current configured roots. It is recommended to keep + this to True if not trying validate full path for optimal + performance. + + Returns: + Dictionary with keys: + * asset_id -> Id of asset + * revision_id -> Id of revision + * version_id -> Id of version + * component_name -> Name of component + * blob_index -> Index into binary array of component + + or None if the file path cannot be identified. + """ + file_path = cleanpath(file_path) + + # Absolute path pattern (expecting a root) + expr = r".*(?P/[^/]+/((r\d+)|(draft))/.+)" + m = re.match(expr, file_path) + if not m: + # Relative path pattern (expecting to begin with storage id) + expr = r"(?P[^/]+/((r\d+)|(draft))/.+)" + m = re.match(expr, file_path) + if not m: + # Not an asset path - failed test for overall asset path pattern + return None + comp_path = m.group("comp_path") + + # add prepended / to match expected asset path pattern if not present + if not comp_path.startswith("/"): + comp_path = "/" + comp_path + + # Parse expected pieces of asset path + try: + _, storage_id, rev_num, comp_path = comp_path.split("/", maxsplit=3) + except ValueError: + # Not an asset path - unable to parse into key path components + return None + + # Look up asset based on storage key + try: + asset_id = storage_key_to_asset_id(storage_id) + except FlowError: + # Not an asset path - storage key component does not map to an asset + return None + + # Convert revision number to integer + if rev_num == "draft": + # Not an asset path - draft paths don't count + return None + else: + try: + rev_num = int(rev_num.strip("r")) + except ValueError: + # Not an asset path - non-int value for revision number component + return None + + revision_id = FlowRevision.get_revision_id(asset_id, rev_num) + try: + # NOTE: Using accessor method rather than constructing new instance + # because this will return a cached object if it exists + # This is ok since revisions are immutable. + revision = FlowRevision.get_revision(revision_id) + except FlowError: + # Not an asset - revision number is out of range + return None + + # Determine component and blob index based on component path + # Try and match against existing binary components on revision + component = blob_index = None + bin_comps = revision.get_binary_components() + if "%" in comp_path: + # File sequence paths will be stored as a zip file + comp_path, _, _ = comp_path.rsplit(".", maxsplit=2) + comp_path += ".zip" + for comp in bin_comps: + for i, blob in enumerate(comp.blobs): + if blob.path == comp_path: + component = comp + blob_index = i + break + if component is None: + # Not an asset path - no component blob matches file + return None + + return { + "asset_id": asset_id, + "revision_id": revision_id, + "version_id": revision.version_id, + "component_name": component.name, + "blob_index": blob_index, + } diff --git a/python/tk-core/python/tank_vendor/flow_integration_sdk/exceptions.py b/python/tk-core/python/tank_vendor/flow_integration_sdk/exceptions.py new file mode 100644 index 00000000..76b017d7 --- /dev/null +++ b/python/tk-core/python/tank_vendor/flow_integration_sdk/exceptions.py @@ -0,0 +1,163 @@ +# ***************************************************************************** +# Copyright 2026 Autodesk, Inc. All rights reserved. +# +# These coded instructions, statements, and computer programs contain +# unpublished proprietary information written by Autodesk, Inc. and are +# protected by Federal copyright law. They may not be disclosed to third +# parties or copied or duplicated in any form, in whole or in part, without +# the prior written consent of Autodesk, Inc. +# ***************************************************************************** +# + + +""" +This module contains custom exception classes. +""" + +from __future__ import annotations # needed for python 3.9 support + + +class FlowError(Exception): + """Base error class for Flow asset management errors.""" + + def __init__( + self, message, *args, details: str = "", data: dict | None = None, **kwargs + ): + """ + Args: + message: Base message for exception. + details: Additional information. + data: Any additional data relevant to the error. + Stored as a dictionary of key-value pairs. + """ + if details: + message = f"{message} {details}" + super().__init__(message, *args, **kwargs) + self.data = data + + +class CreateAssetError(FlowError): + def __init__(self, *args, **kwargs): + message = "Could not create asset." + super().__init__(message, *args, **kwargs) + + +class ComponentSpecError(FlowError): + def __init__(self, *args, **kwargs): + message = "Invalid component spec provided." + super().__init__(message, *args, **kwargs) + + +class ConfigurationError(FlowError): + def __init__(self, *args, **kwargs): + message = "Flow settings have not been configured correctly." + super().__init__(message, *args, **kwargs) + + +class DirectoryNotCreatedError(FlowError): + def __init__(self, *args, dir_path: str, **kwargs): + message = f'Couldn\'t create directory "{dir_path}".' + super().__init__(message, *args, **kwargs) + + +class DraftExistsError(FlowError): + def __init__(self, *args, draft_id: str, draft_folder: str, **kwargs): + """ + Args: + draft_id: Id that uniquely identifies a draft in local sandbox. + draft_folder: Path to draft folder in local sandbox. + """ + message = f'The draft "{draft_id}" already exists at {draft_folder}.' + super().__init__(message, *args, **kwargs) + self.draft_id = draft_id + self.draft_folder = draft_folder + + +class EntityNotFoundError(FlowError): + """Thrown when a given MEDM entity id is invalid, and cannot be used to + successfully query the entity. + """ + + def __init__(self, *args, entity_id: str, **kwargs): + """ + Args: + entity_id: Id for an Asset or Project. + """ + message = f'Entity id "{entity_id}" is invalid.' + super().__init__(message, *args, **kwargs) + self.entity_id = entity_id + + +class FileUploadError(FlowError): + def __init__(self, *args, file_path: str, **kwargs): + message = f"Error uploading file {file_path}." + super().__init__(message, *args, **kwargs) + + +class InvalidDraftError(FlowError): + def __init__(self, *args, draft_id: str, **kwargs): + """ + Args: + draft_id: Id that uniquely identifies a draft in local sandbox. + """ + message = f'Draft id "{draft_id}" is invalid.' + super().__init__(message, *args, **kwargs) + self.draft_id = draft_id + + +class FlowSchemaBuilderError(FlowError): + """General exception for errors related to SchemaBuilder operations.""" + + def __init__(self, *args, **kwargs): + message = "SchemaBuilder operation failed." + super().__init__(message, *args, **kwargs) + + +class FlowSchemaDisplayDataError(FlowError): + def __init__(self, *args, **kwargs): + message = "Schema display data operation failed." + super().__init__(message, *args, **kwargs) + + +class FlowSchemaError(FlowError): + def __init__(self, *args, **kwargs): + message = "Schema operation failed." + super().__init__(message, *args, **kwargs) + + +class FlowSchemaLibraryError(FlowError): + def __init__(self, *args, **kwargs): + message = "Schema library operation failed." + super().__init__(message, *args, **kwargs) + + +class PublishAssetError(FlowError): + def __init__(self, *args, **kwargs): + message = "Could not publish asset." + super().__init__(message, *args, **kwargs) + + +class PublishConflictError(FlowError): + def __init__( + self, *args, asset, checkout_version: int, checkout_revision: int, **kwargs + ): + message = "A publish conflict has been detected. New revisions of the asset " + message += f"have been published since version {checkout_version} " + message += f"(r{checkout_revision}) was checked out. " + message += f"The latest revision is now {asset.version_number} " + message += f"(r{asset.revision_number})." + + super().__init__(message, *args, **kwargs) + self.asset = asset + self.checkout_version = checkout_version + self.checkout_revision = checkout_revision + + +class ThumbnailError(FlowError): + def __init__(self, *args, revision_id: str, **kwargs): + """ + Args: + revision_id: Id of revision. + """ + message = f"Could not retrieve thumbnail for revision: {revision_id}." + super().__init__(message, *args, **kwargs) + self.revision_id = revision_id diff --git a/python/tk-core/python/tank_vendor/flow_integration_sdk/fetch.py b/python/tk-core/python/tank_vendor/flow_integration_sdk/fetch.py new file mode 100644 index 00000000..a48621cc --- /dev/null +++ b/python/tk-core/python/tank_vendor/flow_integration_sdk/fetch.py @@ -0,0 +1,405 @@ +# - +# ***************************************************************************** +# Copyright 2026 Autodesk, Inc. All rights reserved. +# +# These coded instructions, statements, and computer programs contain +# unpublished proprietary information written by Autodesk, Inc. and are +# protected by Federal copyright law. They may not be disclosed to third +# parties or copied or duplicated in any form, in whole or in part, without +# the prior written consent of Autodesk, Inc. +# ***************************************************************************** +# + + +""" +This module provides asset download/fetching utilities. +""" + +from __future__ import annotations # needed for python 3.9 support + +import fileseq +import os +import zipfile +from collections.abc import Iterator +from functools import cache + +from tank_vendor.flow_data_sdk.base import model as medm_model +from tank_vendor.flow_data_sdk.base.exceptions import GQLAPIError + +from .exceptions import FlowError, ThumbnailError +from .globals import ( + get_client, + FILE_SEQ_TYPE, + THUMBNAIL_PURPOSE, +) +from .schema import get_schema_id +from .storage import ( + _cache_asset_info, + _find_component, + get_storage_component_path, + get_storage_revision_dir, +) +from .utils import cleanpath, download_file, get_logger, trace + + +# urn to url cache - optimization to avoid re-querying urls that are fixed +# Format: key = urn, value = url +_urn_to_url = {} + + +@trace +def fetch_blob_urls(project_id: str, urns: list[str]) -> list[str]: + """Query list of urls for given blob urns that can be used for + downloading. Order should be preserved. + + Args: + project_id: Id of project to which binary components belong. + urns: List of blob urns to be converted to urls. + + Returns: + List of urls from which blobs can be downloaded. + + Raises: + FlowError + """ + query_urns = [] # urns to be queried + result_urls = [] # final url list to be returned + + # Add any cached urls to the return result first + # Leave spaces in the list for values that must be queried + for urn in urns: + if urn in _urn_to_url: + result_urls.append(_urn_to_url[urn]) + else: + result_urls.append(None) + query_urns.append(urn) + + # Query the download URLs from the API + if query_urns: + client = get_client() + q_input = medm_model.BinaryComponentUrlsByUrnsInput( + project_id=project_id, + urns=query_urns, + ) + q_urls = client.service_binary.binary_component_urls_by_urns(q_input) + try: + q_urls.call() + except GQLAPIError as exc: + msg = f"Error fetching binary component urls: {exc}" + raise FlowError(msg) from exc + + queried_urls = [bin_comp_url.url for bin_comp_url in q_urls.urls] + + # Cache the urls we just queried + for i, urn in enumerate(query_urns): + _urn_to_url[urn] = queried_urls[i] + + # Merge quered urls with result by filling in the None spaces + # (this should preserve the input order) + for i, url in enumerate(result_urls): + if url is None: + result_urls[i] = queried_urls.pop(0) + + return result_urls + + +@trace +def download( + component: medm_model.Component, + project_id: str, + directory: str, + file_sequence: bool = False, + skip_download: bool = False, +) -> dict[int, str]: + """Download all binary blobs in component to given directory. + Directory must exist, and component must be a binary component. + + Args: + component: Component to be downloaded. + project_id: Project that component belongs to. + directory: Existing folder location to be downloaded to. + file_sequence: If True, expect the component to contain a + zipped file sequence, and automatically expand it. + skip_download: Only relevant for file sequences. Used when + the source zip file has already been downloaded, but + the files haven't been extracted. + + Returns: + Dictionary of blob index to full path of downloaded file. + + Raises: + FlowError + """ + # Get list of urls for each component blob + urns = [blob["uri"] for blob in component.data.get("data", [])] + urls = fetch_blob_urls(project_id, urns) + + result = {} + for i, url in enumerate(urls): + # Determine destination path + # NOTE: this blob index is guaranteed to exist because we retrieved its url + blob_path = component.data["data"][i]["path"] + file_path = cleanpath(directory, blob_path) + + # Finally download the file and save to disk + try: + if not skip_download: + download_file(url, file_path) + except Exception as exc: # pylint: disable=broad-except + raise FlowError( + f'Failed to download blob {i} with url "{url}". {exc}' + ) from exc + result[i] = file_path + + if file_sequence: + # NOTE: This is a temporary solution. When we cease to zip + # up file sequences, this code block and parameter can be removed! + zip_file_path = result[0] + with zipfile.ZipFile(zip_file_path, "r") as zip_file: + # Update result dictionary to reflect frames extracted + # (Do this to mimic the future behaviour where each frame + # will be stored as its own blob) + result = {} + blob_index = 0 + for file_name in zip_file.namelist(): + result[blob_index] = cleanpath(directory, file_name) + blob_index += 1 + # Check for unsafe file paths within zip + # We don't want to allow extracting outside of download directory + if file_name.startswith("/") or ".." in file_name: + msg = f"Unsafe file path detected in zip file: {file_name} - aborting extraction." + raise FlowError(msg) + # Extract the files + zip_file.extractall(directory) + + return result + + +@trace +def fetch( + revision: medm_model.AssetRevision, + component_name: str = "", + component_purpose: str = "", + fetch_dependencies: bool = False, +): + """Fetch the given component of a revision if not already on disk. + If the specified component does not exist, nothing will happen. + + Args: + revision: Revision whose component should be fetched. + component_name: If provided, search for component with this name to be fetched. + This should be unique within the revision. + component_purpose: If provided, search for a component with this purpose to be fetched. + There may be multiple components with the same purpose, + so the first match will be returned. + fetch_dependencies: If True, also fetch the full dependency tree of the revision + using the same component criteria. + + ..note:: If both component name and purpose are provided, the first intersection + of both criteria will be returned. + """ + logger = get_logger(__name__) + + # Get project id + project_id = _get_project_id(revision.id) + + # List of revisions to be fetched + rev_list = [revision] + if fetch_dependencies: + # Add any dependencies (this will include entire dependency tree) + # NOTE: the uses query will return the input revision itself so this + # item will be duplicated in the list, however that should not cause + # a problem. + rev_list.extend(list(_iterate_uses(revision.numbered_version_id))) + + def missing_seq_files(file_seq_comp): + # Return True if any files from the file seq are missing + file_list = _get_file_list(file_seq_comp) + # Convert to absolute paths + storage_dir = get_storage_revision_dir(rev.asset_id, rev.revision_number) + file_list = [cleanpath(storage_dir, f) for f in file_list] + missing_files = False + for f in file_list: + if not os.path.exists(f): + missing_files = True + break + return missing_files + + for rev in rev_list: + # Check that revision has the specified component + # NOTE: making the assumption here that we only have a single blob + comp = _find_component(rev, name=component_name, purpose=component_purpose) + if comp is None: + # If the component doesn't exist, we will log a warning + # but not consider it a failure. The purpose of this function + # is to fetch stored binaries, so if there are no stored + # binaries, the point is moot. + msg = "No component of " + msg += f'name "{component_name}" ' if component_name else "" + msg += f'purpose "{component_purpose}" found in revision "{rev.name}".' + msg += "Skipping fetch for this revision..." + logger.warning(msg) + continue + # NOTE: the component is guaranteed to exist because we already found + # it - can assume this path will not be None + cache_source_path = get_storage_component_path(rev, component_name=comp.name) + file_seq_comp = _find_component(rev, type_id=get_schema_id(FILE_SEQ_TYPE)) + # Check primary storage for source path before fetching + if file_seq_comp and not missing_seq_files(file_seq_comp): + # For file sequences, it's not good enough to check for just + # the source zip file, but we should also make sure its been unzipped + continue + elif not file_seq_comp and os.path.exists(cache_source_path): + continue + + msg = f"Fetching binaries for revision {rev.name} - r{rev.revision_number}" + msg += f', component "{comp.name}".' + logger.info(msg) + + # Fetch the component (indicate if it's a file sequence) + download( + comp, + project_id, + get_storage_revision_dir(rev.asset_id, rev.revision_number), + file_sequence=file_seq_comp is not None, + # NOTE: we may be in a situation where the source file already exists + # but its a file seq that needs to be unzipped. In this case, + # indicate that the download step can be skipped. + skip_download=os.path.exists(cache_source_path), + ) + + # Once fetched, cache asset info in storage dir if necessary + _cache_asset_info(rev.asset_id) + + +@trace +def get_thumbnail_file(revision: medm_model.AssetRevision) -> str: + """Return the path to the thumbnail file on disk. Fetch the file if necessary. + + Args: + revision: Revision whose thumbnail should be fetched. + + Returns: + File path to thumbnail. + + Raises: + ThumbnailError + """ + # Check that thumbnail component exists and + # get path to thumbnail path of revision in local storage + thumbnail_comp = _find_component(revision, purpose=THUMBNAIL_PURPOSE) + if thumbnail_comp is None: + msg = "Revision does not have a thumbnail component." + raise ThumbnailError(revision_id=revision.id, details=msg) + + # Fetch thumbnail component of revision + fetch(revision, component_purpose=THUMBNAIL_PURPOSE) + + # Verify that fetch was successful + file_path = get_storage_component_path(revision, component_name=thumbnail_comp.name) + if not os.path.exists(file_path): + msg = f"Thumbnail file does not exist in storage: {file_path}" + raise ThumbnailError(revision_id=revision.id, details=msg) + + return file_path + + +@trace +def get_thumbnail_url(revision: medm_model.AssetRevision) -> str: + """Return a signed url of the thumbnail for given revision. + + Args: + revision: Revision whose thumbnail should be fetched. + + Returns: + Url of thumbnail. + + Raises: + ThumbnailError + """ + # Get project id + project_id = _get_project_id(revision.id) + + # Check that thumbnail component exists + thumbnail_comp = _find_component(revision, purpose=THUMBNAIL_PURPOSE) + if thumbnail_comp is None: + msg = "Revision has no thumbnail component." + raise ThumbnailError(revision_id=revision.id, details=msg) + + # Fetch the thumbnail's url (assume single blob) + try: + urn = thumbnail_comp.data.get("data", [])[0]["uri"] + return fetch_blob_urls(project_id, [urn])[0] + except (FlowError, IndexError) as exc: + msg = "Could not fetch thumbnail url." + raise ThumbnailError(revision_id=revision.id, details=msg) from exc + + +@cache +@trace +def _iterate_uses(version_id: str) -> Iterator[medm_model.AssetRevision]: + """Query uses relationships in this asset/revision. + Pagination is handled internally within this call via the V2 sdk. + If this query has already been performed, the cached result will be returned. + If refresh=True, do a fresh query. + + NOTE: This implementation grabs the entire uses tree because in practice + this is usually what we need (to fetch dependencies). + + Args: + version_id: Query "uses" dependency tree starting from this version id. + + Returns: + Iterator which handles the paginated response of the query containing + medm_model.AssetRevision objects. + + Raises: + FlowError + """ + client = get_client() + q_input = medm_model.AssetVersionsByTraversalInput( + start_at_id=version_id, # type: ignore[attr-defined] + depth=0, # retrieve entire tree + direction=medm_model.TraverseDirectionEnum.OUTGOING.value, + ) + q_uses = client.service_asset.asset_versions_by_traversal(q_input) + + # Wrap existing iterator in V2 sdk + # NOTE: The iterator function makes the actual query calls so no need to + # explicitly invoke call() here. + try: + # NOTE: We are getting revisions for now because this is most useful to us + # as the primary use case for getting uses information is to fetch + # dependencies. + # + # This is fetched through the versions iterator and not the revisions + # iterator. Since we are querying outgoing edges, the response gives us + # a list of edges like + # Rx -> Vy + # Rx -> Vz + # Rw -> Vu + # ... + # where R = revision, V = version + # The revisions iterator gives us the revision objects associated with + # the source of those edges. The versions iterator gives us the version + # objects associated with the destination of those edges. + for ver in q_uses.versions_iterator: + revision = ver.revision + yield revision + except GQLAPIError as exc: + msg = f"Error querying uses relationships. {exc}" + raise FlowError(msg) from exc + + +def _get_file_list(fileseq_comp: medm_model.Component) -> list[str]: + """Return list of file names described by a file sequence type component.""" + frame_set = fileseq.FrameSet(fileseq_comp.data["frameSet"]) + file_format = fileseq_comp.data["fileFormat"] + file_list = [file_format % i for i in list(frame_set)] + return file_list + + +def _get_project_id(input_id: str) -> str: + """Convert a medm asset/revision/version id into a project id.""" + parts = input_id.split(":") + project_id = f"urn:medm:project:{parts[3]}:{parts[4]}" + return project_id diff --git a/python/tk-core/python/tank_vendor/flow_integration_sdk/globals.py b/python/tk-core/python/tank_vendor/flow_integration_sdk/globals.py new file mode 100644 index 00000000..a86599eb --- /dev/null +++ b/python/tk-core/python/tank_vendor/flow_integration_sdk/globals.py @@ -0,0 +1,215 @@ +# - +# ***************************************************************************** +# Copyright 2026 Autodesk, Inc. All rights reserved. +# +# These coded instructions, statements, and computer programs contain +# unpublished proprietary information written by Autodesk, Inc. and are +# protected by Federal copyright law. They may not be disclosed to third +# parties or copied or duplicated in any form, in whole or in part, without +# the prior written consent of Autodesk, Inc. +# ***************************************************************************** +# + + +""" +This module contains convenience constants and globally accessible +session variables relevant to MEDM access and asset management. +""" + +from __future__ import annotations # needed for python 3.9 support + +from dataclasses import dataclass +from typing import Callable + +from tank_vendor.flow_data_sdk import GQLClient +from tank_vendor.flow_data_sdk.base.client import AuthenticationHandlerBase + +from . import utils +from .exceptions import FlowError +from .utils import get_logger + +# Component type ids +# ------------------ +# Type ids correspond to specific MEDM schemas (and versions). +# Schemas can be created in a hierarchical fashion using inheritance. +# The schemas below are official Autodesk supported types that are +# commonly relevant to asset management. + +BASE_TYPE_ID = "autodesk.me:type-1.1.0" +BINARY_TYPE_ID = "autodesk.me:component.binary-1.0.0" +COMMENT_TYPE_ID = "autodesk.me:component.publishComment-1.0.0" +# NOTE: This is a temporary schema being annexed for representing derivative source +# which should be switched for a dedicated schema later. +DER_SOURCE_TYPE_ID = "autodesk.me:component.dynamicPlaylistSource-1.0.0" +FOLDER_TYPE_ID = "autodesk.me:type.folder-1.0.0" +IMAGE_TYPE_ID = "autodesk.me:component.binary.image-1.0.0" + +# Component types +# --------------- +# Component base type names without full ids. +# This should be a temporary measure, only necessary while some types +# are not yet added to the autodesk domain, and must be created per collection. +FILE_SEQ_TYPE = "type.fileSequence" + + +# Component purposes +# ------------------ +# Purposes are special designations that can be added to a binary component to +# differentiate it based on function/characteristic. Values are completely arbitrary. +# The purposes below are some basic defaults that are generally useful, +# but integrations can choose to use other values. + +SOURCE_PURPOSE = "source" +THUMBNAIL_PURPOSE = "thumbnail" + + +# Component names +# --------------- +# Conventional component names for formally supported components. +# NOTE: Component names must be unique within an asset revision. + +COMMENT_COMP = "Comment" +DER_SOURCE_COMP = "Derivative Source" +FILE_SEQ_COMP = "File Sequence" +SOURCE_COMP = "Source" +THUMBNAIL_COMP = "Thumbnail" +TYPE_COMP = "Type" + + +# MEDM GQL Client Access +# ---------------------- +# Global gql client instance that is initialized per session. +# This variable is for internal use only and should be initialized +# explicitly using the init_client() function, and accessed via get_client(). + +_gql_client: GQLClient | None = None + + +def init_client(endpoint_url: str, auth_handler: AuthenticationHandlerBase): + """Initialize a global client instance that can be reused for the session. + Client object can be accessed using `get_client()` function. + + Args: + endpoint_url: Endpoint to connect to. + auth_handler: An instance of a subclass of AuthenticationHandlerBase + with required authentication interface implemented. + """ + global _gql_client + + logger = get_logger(__name__) + logger.info(f"Creating V2 GQL client with endpoint: {endpoint_url}") + _gql_client = GQLClient(endpoint=endpoint_url, auth_handler=auth_handler) + + +def get_client() -> GQLClient: + """Return global client if initialized, otherwise raises an error. + + Raises: + FlowError + """ + if _gql_client is None: + raise FlowError("GQL client has not been initialized.") + return _gql_client + + +# MEDM Session Collection +# ---------------------- +# The session collection stores the MEDM collection that we are operating +# under for the current session. Tracking this helps us to query schema information +# from the right collection. + +_session_collection: SessionCollection | None = None + + +@dataclass +class SessionCollection: + """Data class containing relevant information for session collection. + It tracks the collection id, and provides easy access to pertinent attributes. + """ + + #: Id of collection + id: str + #: Organization ID of collection + organization_id: str + #: Group Id of collection + group_id: str + + def is_cpa_collection(self) -> bool: + """Return true if session project is from a CPA provisioned collection.""" + if self.organization_id == "fstech": + return False + return True + + def __str__(self): + """Stringify object info in readable way.""" + s = "SESSION COLLECTION:\n" + s += f"\tid: {self.id}\n" + s += f"\torganization_id: {self.organization_id}\n" + s += f"\tgroup_id: {self.group_id}\n" + return s + + +def init_session_collection(collection_id: str, organization_id: str, group_id: str): + """Store collection info for session. + + Global SessionCollection data object can be accessed using `get_session_collection()` + function. + + Args: + collection_id: MEDM collection id. + organization_id: Organization id of collection. + group_id: Group id of collection. + """ + global _session_collection + + logger = get_logger(__name__) + logger.info("Setting session collection info...") + _session_collection = SessionCollection( + id=collection_id, + organization_id=organization_id, + group_id=group_id, + ) + logger.info(_session_collection) + + +def get_session_collection() -> SessionCollection: + """Return session collection if initialized, otherwise raises an error. + + Raises: + FlowError + """ + if _session_collection is None: + raise FlowError("Session collection has not been initialized.") + return _session_collection + + +# Web App url +# ----------- +# Store configured web app url as a global for the session. +# This variable is for internal use only. +# Configure this value via set_webapp_url() and access is via get_webapp_url(). + +_webapp_url = None + + +def set_webapp_url(url: str): + """Set global variable for webapp url.""" + global _webapp_url + logger = get_logger(__name__) + logger.info(f"Setting Flow web app url to: {url}") + _webapp_url = url + + +def get_webapp_url() -> str | None: + """Return configured webapp url or None if it is not set.""" + return _webapp_url + + +# Configure logger +# ---------------- +# Set a logger callback for use in the integration sdk. +# If not configured, a default python logger will be used. + + +def set_logger_callback(callback: Callable): + """Set the function that returns the logger for the session.""" + utils._logger_callback = callback diff --git a/python/tk-core/python/tank_vendor/flow_integration_sdk/objects.py b/python/tk-core/python/tank_vendor/flow_integration_sdk/objects.py new file mode 100644 index 00000000..ad27fcb2 --- /dev/null +++ b/python/tk-core/python/tank_vendor/flow_integration_sdk/objects.py @@ -0,0 +1,1470 @@ +# - +# ***************************************************************************** +# Copyright 2026 Autodesk, Inc. All rights reserved. +# +# These coded instructions, statements, and computer programs contain +# unpublished proprietary information written by Autodesk, Inc. and are +# protected by Federal copyright law. They may not be disclosed to third +# parties or copied or duplicated in any form, in whole or in part, without +# the prior written consent of Autodesk, Inc. +# ***************************************************************************** +# + + +""" +This module contains custom object wrappers representing MEDM data. +The object inferface provides a convenient way to access, query and do paginated +iteration of MEDM data. It also provides an interface to the Flow file storage +solution for seamless access to both the data model and associated binaries. +""" + +from __future__ import annotations # needed for python 3.9 support + +import datetime +import re +import urllib +from collections.abc import Iterator +from functools import cache +from typing import Any + +from tank_vendor.flow_data_sdk.base import model as medm_model +from tank_vendor.flow_data_sdk.base.exceptions import GQLAPIError + +from .exceptions import ( + EntityNotFoundError, + FlowError, +) +from .fetch import ( + download, + fetch, + fetch_blob_urls, + get_thumbnail_file, + get_thumbnail_url, +) +from .globals import ( + BASE_TYPE_ID, + BINARY_TYPE_ID, + COMMENT_TYPE_ID, + DER_SOURCE_TYPE_ID, + get_client, + get_webapp_url, +) +from .sandbox import CheckoutDraftInfo, get_asset_drafts +from .storage import ( + _cache_asset_info, + get_storage_asset_dir, + get_storage_component_path, + get_storage_key, + get_storage_revision_dir, +) +from .utils import ( + get_logger, + to_regex_safe_wildcard_string, + trace, +) + + +class FlowEntity: + """Base class with functionality pertinent to MEDM entities. + This includes Assets and Projects, and basically entails having containership + capabilities (i.e. having children). + """ + + def __init__(self, entity: medm_model.Asset | medm_model.Project, **kwargs): + """ + Args: + entity: The medm entity that this object represents. + """ + # The medm entity that this class represents + # This should be a medm_model.Asset or medm_model.Project + # Storing this object so we can access its properties + self._entity = entity + + # Query objects - initialized on demand + # NOTE: In V2 api, query objects should be instantiated every call + # and persist the results of that query for the object's lifetime. + self._q_children: Any = None + + super().__init__(**kwargs) + + @property + def id(self) -> str: + """Return MEDM id of entity.""" + return self._entity.id + + @property + def name(self) -> str: + """Return MEDM name of entity.""" + return self._entity.name + + @trace + def iterate_children(self, refresh: bool = False) -> Iterator[FlowAsset]: + """Query assets contained in this entity. + Pagination is handled internally within this call via the V2 sdk. + If this query has already been performed, the cached result will be returned. + If refresh=True, do a fresh query. + + Raises: + FlowError + """ + # Construct new "contains" query if necessary + if self._q_children is None or refresh: + client = get_client() + q_input = medm_model.AssetsByTraversalInput( + start_at_id=self.id, + depth=1, + direction=medm_model.TraverseDirectionEnum.OUTGOING.value, + ) + self._q_children = client.service_asset.assets_by_traversal(q_input) + + # Wrap existing iterator in V2 sdk + try: + for child in self._q_children.assets_iterator: + # Skip self in the results (traversal API includes start node) + if child.id == self.id: + continue + yield FlowAsset(child) # convert to custom Asset object + except GQLAPIError as exc: + msg = f'Error querying children of entity "{self.name}". {exc}' + raise FlowError(msg) from exc + + @trace + def find_child(self, name: str, force_query: bool = False) -> FlowAsset | None: + """Find the child that has the given name. + + .. note:: The first match found will be returned. + + Args: + name: Name to be matched. + force_query: Force a new query of the asset's children. + If False, previous query results will be used if available. + + Returns: + The FlowAsset entity if found, or None. + + Raises: + FlowError + """ + for child in self.iterate_children(refresh=force_query): + if child.name == name: + return child + return None + + @trace + def find_children( + self, + name: str = "", + type_id: str = "", + force_query: bool = False, + ) -> list[FlowAsset]: + """Return any children that matches the given criteria. + + Args: + name: Name to be matched. Can support wildcard character '*'. + If blank ignore this filter. + type_id: Match children marked as this type (sub-types will be included). + If blank ignore this filter. + force_query: Force a new query of the asset's children. + If False, previous query results will be used if available. + Returns: + List of FlowAsset objects. + + Raises: + FlowError + """ + matches = [] + regex = "^{}$".format(to_regex_safe_wildcard_string(name)) + for child in self.iterate_children(refresh=force_query): + if name and not re.match(regex, child.name): + continue + if type_id and not child.find_component(type_id=type_id): + continue + matches.append(child) + return matches + + +class UsesMixin: + """Mixin class with convenience functionality pertinent to MEDM objects + that contain "uses" relationships (i.e. dependencies on other MEDM assets). + This includes FlowAssets and FlowRevisions. + """ + + @trace + def init_uses(self, version_id: str): + """Explicit initialization function to store pertinent info. + This should be called in the __init__() function of any inheriting classes. + + Args: + version_id: Version id of asset or revision. + """ + self._uses_version_id = version_id + + # Query objects - initialized on demand + # NOTE: In V2 api, query objects should be instantiated every call + # and persist the results of that query for the object's lifetime. + self._q_uses: Any = None + + @trace + def iterate_uses(self, refresh: bool = False) -> Iterator[FlowRevision]: + """Query uses relationships in this asset/revision. + Pagination is handled internally within this call via the V2 sdk. + If this query has already been performed, the cached result will be returned. + If refresh=True, do a fresh query. + + NOTE: This implementation grabs the entire uses tree because in practice + this is usually what we need (to fetch dependencies). + + Raises: + FlowError + """ + # Construct new "uses" query if necessary + if self._q_uses is None or refresh: + client = get_client() + q_input = medm_model.AssetVersionsByTraversalInput( + start_at_id=self._uses_version_id, # type: ignore[attr-defined] + depth=0, # retrieve entire tree + direction=medm_model.TraverseDirectionEnum.OUTGOING.value, + ) + self._q_uses = client.service_asset.asset_versions_by_traversal(q_input) + + # Wrap existing iterator in V2 sdk + # NOTE: The iterator function makes the actual query calls so no need to + # explicitly invoke call() here. + try: + # NOTE: We are getting revisions for now because this is most useful to us + # as the primary use case for getting uses information is to fetch + # dependencies. + # + # This is fetched through the versions iterator and not the revisions + # iterator. Since we are querying outgoing edges, the response gives us + # a list of edges like + # Rx -> Vy + # Rx -> Vz + # Rw -> Vu + # ... + # where R = revision, V = version + # The revisions iterator gives us the revision objects associated with + # the source of those edges. The versions iterator gives us the version + # objects associated with the destination of those edges. + for ver in self._q_uses.versions_iterator: + revision = FlowVersion(ver).revision + yield revision + except GQLAPIError as exc: + msg = f"Error querying uses relationships. {exc}" + raise FlowError(msg) from exc + + +class ComponentMixin: + """Mixin class with convenience functionality pertinent to MEDM objects + that contain components. This includes FlowAssets and FlowRevisions. + """ + + @trace + def init_components(self, components: list[medm_model.Component]): + """Explicit initialization function to ingest component data. + This should be called in the __init__() function of any inheriting classes. + + Args: + components: List of medm_model.Component objects to be translated and stored + as internal Component objects. Must not be None. + + Raises: + FlowError: If components is None or any component data is invalid. + """ + if components is None: + raise FlowError( + "Components cannot be None. This indicates the query did not " + "include the components field, or the SDK returned NOT_SET." + ) + + self.components = [] # List of components encoded as custom objects + for medm_comp in components: + self.components.append(FlowComponent(self, medm_comp)) + + @trace + def get_binary_components(self) -> list[FlowComponent]: + """Search for all binary components on asset/revision and return. + + Returns: + Return a list of Components whose type id inherits from BINARY_TYPE_ID. + Empty list implies none found. + """ + type_comps = [] + for comp in self.components: + if BINARY_TYPE_ID in comp.parent_type_ids: + type_comps.append(comp) + return type_comps + + @trace + def get_type_components(self) -> list[FlowComponent]: + """Search for all type components on asset/revision and return. + + Returns: + Return a list of Components whose type id inherits from BASE_TYPE_ID. + Empty list implies none found. + """ + type_comps = [] + for comp in self.components: + if BASE_TYPE_ID in comp.parent_type_ids: + type_comps.append(comp) + return type_comps + + @trace + def get_type_ids(self) -> list[str]: + """Return the type ids of the given asset/revision. + + Returns: + Return a list of type ids associated with type components found on asset/revision. + Empty list implies no types are assigned to asset. + """ + type_comps = self.get_type_components() + return [comp.type_id for comp in type_comps] + + @trace + def find_components( + self, + name: str = "", + purpose: str = "", + type_id: str = "", + ) -> list[FlowComponent]: + """Search for component with matching name in given revision. + + ..note:: Filters, if defined, are treated as an intersection, meaning + results will conform to all filters. + + Args: + name: Component name to match. '*' wild card supported. + If blank ignore this filter. + purpose: Match this purpose on component. If blank ignore this filter. + type_id: Match this type id on component. If blank ignore this filter. + + Returns: + Component object or None if not found. + """ + regex = "^{}$".format(to_regex_safe_wildcard_string(name)) + matches = [] + for comp in self.components: + if name and not re.match(regex, comp.name): + continue + if purpose and purpose != comp.purpose: + continue + if type_id and type_id not in comp.parent_type_ids: + continue + matches.append(comp) + return matches + + @trace + def find_component( + self, + name: str = "", + purpose: str = "", + type_id: str = "", + ) -> FlowComponent | None: + """Search for component matching criteria in the same way as described in + `find_components()`, but return only the first match. + + Args: + See `find_components()` documentation. + + Returns: + Component object or None if not found. + """ + matches = self.find_components(name, purpose, type_id) + if matches: + return matches[0] + return None + + +class FlowProject(FlowEntity): + """Container class for data relevant to a particular medm_model.Project. + Stores various immutable properties to avoid needing to re-query them. + Provides functions for obtaining extra data about the medm_model.Project. + """ + + # MEDM entity name + MEDM_ENTITY = "project" + + @classmethod + def is_project_id(cls, project_id: str): + """Return True if given id conforms to expected structure for + MEDM project ids. + """ + if not project_id.startswith(f"urn:medm:{cls.MEDM_ENTITY}:"): + return False # does not have correct header + parts = project_id.split(":") + if len(parts) != 5: + return False # does not have correct segments + return True + + @classmethod + def get_project_id(cls, input_id: str) -> str: + """Convert an asset, revision or version id to a project id. + + Args: + input_id: An MEDM asset, revision or version id. + + Returns: + An MEDM project id. + + Raises: + ValueError + """ + if not FlowAsset.is_asset_id(input_id): + if not FlowRevision.is_revision_id(input_id): + if not FlowVersion.is_version_id(input_id): + msg = f"Invalid input id provided: {input_id}. " + msg += "Input must be an asset, version or revision id." + raise FlowError(msg) + + parts = input_id.split(":") + project_id = f"urn:medm:project:{parts[3]}:{parts[4]}" + return project_id + + @classmethod + def get_collection_id(cls, input_id: str) -> str: + """Return the collection id of the collection session project belongs to. + + Args: + input_id: An MEDM project, asset, revision or version id. + + Raises: + FlowError + """ + try: + prefix, col_id, sub_id = input_id.rsplit(":", maxsplit=2) + except ValueError as exc: + msg = f'Input id "{input_id}" is invalid. {exc}' + raise FlowError(msg) from exc + return col_id + + @trace + def __init__(self, project: str | medm_model.Project): + """ + Args: + project: Either an MEDM project id, or an medm_model.Project object. + + Raises: + EntityNotFoundError + FlowError + """ + logger = get_logger(__name__) + + if isinstance(project, str): + if not self.is_project_id(project): + msg = f"Invalid project id provided: {project}" + raise FlowError(msg) + # Query medm project by id + client = get_client() + q_input = medm_model.ProjectsByIdsInput(ids=[project]) + q_project = client.service_project.projects_by_ids(q_input) + try: + q_project.call() + except GQLAPIError as exc: + msg = f"Error querying project: {project}. {exc}" + raise FlowError(msg) from exc + if len(q_project.projects) == 0: + msg = "Error retrieving MEDM project." + raise EntityNotFoundError(entity_id=project, details=msg) + project = q_project.projects[0] + logger.info(f'Queried project "{project.name}".') + elif not isinstance(project, medm_model.Project): + msg = "Project error: input provided is not an medm_model.Project or project id." + raise FlowError(msg) + + # Trigger Entity class initialization + super().__init__(project) + + @property + def collection_id(self) -> str: + """The id of the collection to which this project belongs.""" + return self.get_collection_id(self.id) + + @property + def organization_id(self) -> str: + """Organization id of collection to which project belongs. + NOTE: This is useful for schema id determination. + """ + return self._entity.schema_registry_info.organization_id + + @property + def group_id(self) -> str: + """Group id of collection to which project belongs. + NOTE: This is useful for schema id determination. + """ + return self._entity.schema_registry_info.group_id + + def __str__(self): + """Readable string representation of project object.""" + s = "------------------------------\n" + s += f"PROJECT: {self.name}\n" + s += "------------------------------\n" + s += f" id: {self.id}\n" + s += f" organization_id: {self.organization_id}\n" + s += f" group_id: {self.group_id}\n" + return s + + +class FlowAsset(ComponentMixin, UsesMixin, FlowEntity): + """Container class for data relevant to a particular medm_model.Asset. + Stores various immutable properties to avoid needing to re-query them. + Provides functions for obtaining extra data about the medm_model.Asset as well + as publishing capabilities. + """ + + # MEDM entity nmae + MEDM_ENTITY = "asset" + + @classmethod + def is_asset_id(cls, asset_id: str) -> bool: + """Return True if given id conforms to expected structure for + MEDM asset ids. + """ + if not asset_id.startswith(f"urn:medm:{cls.MEDM_ENTITY}:"): + return False # does not have correct header + parts = asset_id.split(":") + if len(parts) != 6: # does not have correct segments + return False + return True + + @classmethod + def get_asset_id(cls, input_id: str) -> str: + """Convert a revision or version id to an asset id. + + Args: + input_id: An MEDM revision or version id. + + Returns: + An MEDM asset id. + + Raises: + ValueError + """ + if not FlowRevision.is_revision_id(input_id): + if not FlowVersion.is_version_id(input_id): + msg = f"Invalid input id provided: {input_id}. " + msg += "Input must be a version or revision id." + raise FlowError(msg) + + parts = input_id.split(":") + asset_id = f"urn:medm:asset:{parts[3]}:{parts[4]}:{parts[5]}" + return asset_id + + @classmethod + def get_web_url(cls, asset_id: str): + """Return url which will open Flow Web App to given asset id. + + ..note:: Please ensure web app base url is configured via + `globals.set_webapp_url()`. + """ + webapp_url = get_webapp_url() + if webapp_url is None: + raise FlowError("Web app url has not been configured.") + webapp_url = webapp_url.rstrip("/") + web_id = urllib.parse.quote(asset_id) + return f"{webapp_url}/assets?id={web_id}" + + @classmethod + def get_drafts(cls, asset_id: str) -> list[CheckoutDraftInfo]: + """Return all local drafts that exist for the given asset. + + .. note:: Currently, we only support a single draft per asset, + however this could change in the future with the introduction + of multiple checkouts/sandboxes. Returning a list keeps this + utility flexible. + + The returned values will be CheckoutDraftInfo objects which will contain + detailed information about each draft. + + Args: + asset_id: Id of MEDM asset. This can also be a revision or version id + from the same asset. + + Returns: + List of CheckoutDraftInfo objects. + + Raises: + FlowError + """ + # Convert to asset id if necessary + if FlowRevision.is_revision_id(asset_id) or FlowVersion.is_version_id(asset_id): + asset_id = FlowAsset.get_asset_id(asset_id) + elif not FlowAsset.is_asset_id(asset_id): + msg = f"Invalid input id provided: {asset_id}. " + msg += "Input must be an asset, revision or version id." + raise FlowError(msg) + return get_asset_drafts(asset_id) + + @trace + def __init__(self, asset: str | medm_model.Asset): + """ + Args: + asset: Either an MEDM asset id, or an medm_model.Asset object. + + Raises: + EntityNotFoundError + FlowError + """ + logger = get_logger(__name__) + + if isinstance(asset, str): + if not self.is_asset_id(asset): + msg = f"Invalid asset id provided: {asset}" + raise FlowError(msg) + client = get_client() + q_input = medm_model.AssetsByIdsInput(ids=[asset]) + q_asset = client.service_asset.assets_by_ids(q_input) + try: + q_asset.call() + except GQLAPIError as exc: + msg = f"Error querying asset: {asset}. {exc}" + raise FlowError(msg) from exc + if len(q_asset.assets) == 0: + msg = "Error retrieving MEDM asset." + raise EntityNotFoundError(entity_id=asset, details=msg) + asset = q_asset.assets[0] + logger.info(f'Queried asset "{asset.name}".') + elif not isinstance(asset, medm_model.Asset): + msg = "Asset error: input provided is not an medm_model.Asset or asset id." + raise FlowError(msg) + + # Store general entity information + super().__init__(asset) + # Initialize ComponentMixin class + self.init_components(asset.components) + # Initialize UsesMixin class + self.init_uses(asset.numbered_version_id) + + # Cache relevant data about this asset for future use + _cache_asset_info(self.id) + + # Query objects - initialized on demand + # NOTE: In V2 api, query objects should be instantiated every call + # and persist the results of that query for the object's lifetime. + self._q_revisions: Any = None + self._q_versions: Any = None + + @property + def storage_key(self) -> str: + """Storage key of asset.""" + return get_storage_key(self.id) + + @property + def parent_id(self) -> str: + """MEDM id of parent entity (could be an asset or project).""" + return self._entity.parent_id + + @property + def project_id(self) -> str: + """MEDM id of containing project.""" + return self._entity.project_id + + @property + def revision_id(self) -> str: + """Revision id of latest revision of asset at the time of + object creation or publish. + """ + return self._entity.revision_id + + @property + def revision_number(self) -> int: + """Revision number of latest revision of asset at the time of + object creation or publish. + """ + return self._entity.revision_number + + @property + def version_id(self) -> str: + """Id of latest (numbered) version of asset at time of + object creation or publish. + """ + return self._entity.numbered_version_id + + @property + def version_number(self) -> int: + """Number of latest (numbered) version of asset at time of + object creation or publish. + """ + return self._entity.version_number + + @property + def description(self) -> str | None: + """Description of asset.""" + return self._entity.description + + @property + def created_at(self) -> datetime.datetime: + """Creation timestamp of this asset.""" + return datetime.datetime.fromisoformat( + self._entity.created.date.replace("Z", "+00:00") + ) + + @property + def created_by(self) -> str | None: + """Username of creator of this asset.""" + if self._entity.created.user: + return self._entity.created.user.user_name + return None + + @property + @cache + def type_ids(self) -> list[str]: + """Return type ids explicitly assigned to asset. + (This will not include base types.) + """ + return self.get_type_ids() + + def get_storage_dir(self) -> str: + """Return the full path of asset directory in primary storage + (whether or not the directory exists). + + Returns: + Full path to expected location of primary storage directory on local disk. + """ + return get_storage_asset_dir(self.id) + + def get_storage_revision_dir(self) -> str: + """Return the full path of asset directory in primary storage + for the latest revision of asset (whether or not the directory exists). + + Returns: + Full path to expected location of primary storage directory on local disk. + """ + return get_storage_revision_dir(self.id, self.revision_number) + + @trace + def get_parent(self) -> FlowAsset | FlowProject: + """Return parent of this asset. + + Raises: + FlowError + """ + try: + if self.parent_id == self.project_id: + return FlowProject(self.parent_id) + else: + return FlowAsset(self.parent_id) + except FlowError as exc: + msg = f'Could not retrieve parent for asset "{self.name}".' + raise FlowError(msg, details=str(exc)) from exc + + @trace + def get_latest_revision(self) -> FlowRevision: + """Return an object representing the latest revision of this asset.""" + return FlowRevision.get_revision(self.revision_id) + + @trace + def iterate_revisions(self, refresh: bool = False) -> Iterator[FlowRevision]: + """Query revisions of this asset. + Pagination is handled internally within this call via the V2 sdk. + If this query has already been performed, the cached result will be returned. + If refresh=True, do a fresh query. + + Raises: + FlowError + """ + # Construct new "revisions" query if necessary + if self._q_revisions is None or refresh: + client = get_client() + q_input = medm_model.AssetRevisionsByAssetIdInput(asset_id=self.id) + self._q_revisions = ( + client.service_asset_revision.asset_revisions_by_asset_id(q_input) + ) + + # Wrap existing iterator in V2 sdk + try: + for rev in self._q_revisions.asset_revisions_iterator: + revision = FlowRevision(rev) # convert to custom FlowRevision object + yield revision + except GQLAPIError as exc: + msg = f'Error querying revisions of asset "{self.name}". {exc}' + raise FlowError(msg) from exc + + @trace + def iterate_versions(self, refresh: bool = False) -> Iterator[FlowVersion]: + """Query numbered versions for this asset using V2 GraphQL API. + Pagination is handled internally within this call via the V2 sdk. + If this query has already been performed, the cached result will be returned. + If refresh=True, do a fresh query. + + Only returns NumberedAssetVersion (v1, v2, v3...), not NamedAssetVersion ("latest"), + since NamedAssetVersion doesn't have direct revision access. + + Args: + refresh: If True, force a fresh query. If False, use cached results if available. + + Yields: + AssetVersion objects wrapping NumberedAssetVersion, ordered by version number + in descending order (newest first: v3, v2, v1). + + Raises: + FlowError + """ + # Construct new versions query if necessary + if self._q_versions is None or refresh: + client = get_client() + sort_input = medm_model.SortInput( + field="created.date", order=medm_model.SortOrderEnum.DESC + ) + q_input = medm_model.AssetVersionsByAssetIdInput( + asset_id=self.id, sort=sort_input + ) + self._q_versions = client.service_asset.asset_versions_by_asset_id(q_input) + + # Wrap existing iterator in V2 sdk + try: + for asset_version in self._q_versions.asset_versions_iterator: + # Skip NamedAssetVersion (like "latest" - they're just pointers) + if isinstance(asset_version, medm_model.NumberedAssetVersion): + yield FlowVersion(asset_version) + except GQLAPIError as exc: + msg = f'Error querying versions for asset "{self.name}". {exc}' + raise FlowError(msg) from exc + + @trace + def find_derivative( + self, + target_type_id: str, + target_component_name: str, + ) -> FlowAsset | None: + """Search asset to find an outbound derivative where the target + matches the criteria provided. + + This function searches across ALL revisions of the source asset to find + any existing derivative relationship. + + Args: + target_type_id: Type of target revision. + target_component_name: Name of component on target revision to be matched. + (Derivative relationships are component to component.) + + Returns: + The first derivative asset found, or None. + """ + # This target id should match the beginning of any revision id belonging to this asset + target_id = self.id.replace(self.MEDM_ENTITY, FlowRevision.MEDM_ENTITY) + + # Generate a query to find assets which contain a source-derivative + # component with a matching target id + # Since we know derivative assets will be siblings of the current asset + # we can safely scope this query to the parent asset with depth of 1. + client = get_client() + q_filter = f"has.component.type=={DER_SOURCE_TYPE_ID};" + q_filter += f"components[typeId:{DER_SOURCE_TYPE_ID}].data.folder.objectId=like={target_id}*;" + q_filter += ( + f"components[typeId:{DER_SOURCE_TYPE_ID}].name=='{target_component_name}'" + ) + q_input = medm_model.AssetsByTraversalInput( + start_at_id=self.parent_id, # search under parent + depth=1, # search immediate children only + direction=medm_model.TraverseDirectionEnum.OUTGOING.value, + filters=q_filter, + ) + q_derivatives = client.service_asset.assets_by_traversal(q_input) + + try: + q_derivatives.call() + except GQLAPIError as exc: + msg = f'Derivative query failed for asset "{self.name}". {exc}' + raise FlowError(msg) from exc + + # NOTE: the starting asset (i.e. parent) will always be returned in + # the asset list, so we must skip that one + der_assets = [ + FlowAsset(a) for a in q_derivatives.assets if a.id != self.parent_id + ] + # Now filter out derivatives of the wrong type + der_assets = [a for a in der_assets if target_type_id in a.type_ids] + + if len(der_assets) > 0: + return der_assets[0] + + return None + + def __str__(self): + """Readable string representation of asset object.""" + s = "------------------------------\n" + s += f"ASSET: {self.name}\n" + s += "------------------------------\n" + s += f" id: {self.id}\n" + s += f" storage_key: {self.storage_key}\n" + s += f" version: {self.version_number}\n" + s += f" revision: {self.revision_number}\n" + s += f" type_ids: {self.type_ids}\n" + return s + + +class FlowRevision(ComponentMixin, UsesMixin): + """Container class for data relevant to a particular medm_model.AssetRevision. + Stores various immutable properties to avoid needing to re-query them. + Since revisions are by definition immutable, this class stores a cache + of all previously accessed revisions so that they are never queried more than once. + """ + + # MEDM entity name + MEDM_ENTITY = "assetRevision" + + # Dictionaries that track already accessed revisions for reuse + # key = revision ids, value = FlowRevision object + _revision_cache: dict[str, FlowRevision] = {} + + @classmethod + def is_revision_id(cls, revision_id: str) -> bool: + """Return True if given id conforms to expected structure for + MEDM revision ids. + """ + if not revision_id.startswith(f"urn:medm:{cls.MEDM_ENTITY}:"): + return False # does not have correct header + parts = revision_id.split(":") + if len(parts) != 8: + return False # does not have correct segments + if parts[-2] != "rev": + return False # does not fit revision format + try: + int(parts[-1]) + except ValueError: + return False # does not have a valid revision number + return True + + @classmethod + def get_revision_id(cls, asset_id: str, revision_number: int) -> str: + """Generate a revision id based on inputs. + NOTE: resultant revision id will be structurally valid, but + may not actually exist. + + Args: + asset_id: MEDM asset id. + revision_number: Number of revision within asset. + + Returns: + MEDM revision id. + """ + if not FlowAsset.is_asset_id(asset_id): + raise FlowError(f"Invalid asset id provided: {asset_id}") + + # Remove asset header in id + asset_id = asset_id.split(":", maxsplit=3)[-1] + + return f"urn:medm:{cls.MEDM_ENTITY}:{asset_id}:rev:{revision_number}" + + @classmethod + def get_revision_number(cls, revision_id: str) -> int: + """Return revision number based on id.""" + if not cls.is_revision_id(revision_id): + raise FlowError(f"Invalid revision id provided: {revision_id}") + # NOTE: since id passed validation, there should be no errors here + return int(revision_id.split(":")[-1]) + + @classmethod + def get_revision(cls, revision: str | medm_model.AssetRevision) -> FlowRevision: + """Retrieve a cached FlowRevision object based on provided revision id + or create a new FlowRevision object to represent the revision. + + Args: + revision: Either a string medm revision id, or a medm_model.AssetRevision object. + + Returns: + FlowRevision object. + + Raises: + EntityNotFoundError + FlowError + """ + if isinstance(revision, medm_model.AssetRevision): + revision_id = revision.id + else: + revision_id = revision + + # Return cached value if available + if revision_id in cls._revision_cache: + return cls._revision_cache[revision_id] + + # Convert to custom object + return FlowRevision(revision) + + @trace + def __init__(self, revision: str | medm_model.AssetRevision): + """ + Args: + revision: Either an MEDM revision id, or an medm_model.AssetRevision object. + + Raises: + EntityNotFoundError + FlowError + """ + logger = get_logger(__name__) + + if isinstance(revision, str): + if not self.is_revision_id(revision): + msg = f"Invalid revision id provided: {revision}" + raise FlowError(msg) + client = get_client() + q_input = medm_model.AssetRevisionsByIdsInput(ids=[revision]) + q_revision = client.service_asset_revision.asset_revisions_by_ids(q_input) + try: + q_revision.call() + except GQLAPIError as exc: + msg = f"Error querying revision: {revision}. {exc}" + raise FlowError(msg) from exc + if len(q_revision.revisions) == 0: + msg = "Error retrieving MEDM revision." + raise EntityNotFoundError(entity_id=revision, details=msg) + revision = q_revision.revisions[0] + msg = f'Queried revision "{revision.name}" revision number {revision.revision_number}.' + logger.info(msg) + elif not isinstance(revision, medm_model.AssetRevision): + msg = "Revision error: input provided is not an medm_model.AssetRevision or revision id." + raise FlowError(msg) + + # The medm revision that this class represents + # This should be a medm_model.AssetRevision + # Storing this object so we can access its properties as needed + self._revision = revision + + # Initialize ComponentMixin class + self.init_components(revision.components) + # Initialize UsesMixin class + self.init_uses(revision.numbered_version_id) + + # Cache object + # Revisions are immutable so we should only ever query each revision once! + FlowRevision._revision_cache[revision.id] = self + + # -------------------------------------------------------------------- + # Enforce read-only properties on revision objects since these objects + # are cached and should be considered immutable. + # -------------------------------------------------------------------- + + @property + def id(self) -> str: + return self._revision.id + + @property + def asset_id(self) -> str: + """Return asset id of revision.""" + return self._revision.asset_id + + @property + def name(self) -> str: + return self._revision.name + + @property + def revision_number(self) -> int: + """Number of this revision. + This value is strictly acsending per revision and begins at 1. + """ + return self._revision.revision_number + + @property + def version_id(self) -> str: + """Return id of "numbered version" to which this revision belongs. + NOTE: Multiple revisions may map to the same numbered version. + The current revision may or may not be the latest of that set. + """ + return self._revision.numbered_version_id + + @property + def version_number(self) -> int: + """Return version number to which this revision belongs. + Version numbers with respect to revisions are non-decreasing, however, + multiple successive revisions may have the same version number. + This value increments beginning at 1. + """ + return self._revision.version_number + + @property + def storage_key(self) -> str: + """Storage key is the asset identifier portion of the full asset id.""" + return get_storage_key(self.asset_id) + + @property + def comment(self) -> str | None: + """Return the publish comment stored in a comment component if it exists.""" + comment_comp = self.find_component(type_id=COMMENT_TYPE_ID) + if comment_comp: + # Will ignore malformed comment components for now + return comment_comp.properties.get("subjectLine") + return None + + def get_storage_dir(self) -> str: + """Return the full path of asset revision directory in primary storage + (whether or not the directory exists). + + Returns: + Full path to expected location of cached storage directory on local disk. + """ + return get_storage_revision_dir(self.asset_id, self.revision_number) + + def get_storage_component_path( + self, + component_name: str = "", + component_purpose: str = "", + blob_index: int = 0, + ) -> str | None: + """Return the full path of the component blob file of the given asset revision + in primary storage (whether or not the file exists). + + Args: + component_name: If provided, search for component with this name and return its storage path. + This should be unique within the revision. + component_purpose: If provided, search for a component with this purpose and return + its storage path. There may be multiple components with the same purpose, + so the first match will be returned. + blob_index: Specific blob from source component to get. + + ..note:: If both component name and purpose are provided, the first intersection + of both criteria will be returned. + + Returns: + Full path to expected location of cached source file on local disk, or + None if the component does not exist on the revision. + """ + return get_storage_component_path( + self._revision, + component_name=component_name, + component_purpose=component_purpose, + blob_index=blob_index, + ) + + @trace + def fetch(self, component_purpose: str, fetch_dependencies: bool = False): + """Fetch the given component of this revision if not already on disk. + If the specified component does not exist, nothing will happen. + + Args: + component_purpose: Fetch component of this purpose. + fetch_dependencies: If True, also fetch components of the same purpose + in this revisions "uses" tree. + (This may not be appropriate for all component purposes.) + """ + fetch( + self._revision, + component_purpose=component_purpose, + fetch_dependencies=fetch_dependencies, + ) + + @trace + def get_thumbnail_file(self) -> str: + """Return the file path to the revision's thumbnail if it exists, + fetching it if necessary. + + Returns: + Full path to thumbnail in storage. + + Raises: + ThumbnailError + """ + return get_thumbnail_file(self._revision) + + @trace + def get_thumbnail_url(self) -> str: + """Return the signed url to the revision's thumbnail if it exists. + + Returns: + Url that can be used to download thumbnail. + + Raises: + ThumbnailError + """ + return get_thumbnail_url(self._revision) + + def __str__(self): + """Readable string representation of revision object.""" + s = "------------------------------\n" + s += f"REVISION: {self.name} - r{self.revision_number}\n" + s += "------------------------------\n" + s += f" id: {self.id}\n" + s += f" storage_key: {self.storage_key}\n" + s += f" version: {self.version_number}\n" + return s + + +class FlowVersion: + """Container class for data relevant to a medm_model.NumberedAssetVersion.""" + + # MEDM entity name + MEDM_ENTITY = "assetVersion" + + @classmethod + def is_version_id(cls, version_id: str) -> bool: + """Return True if given id conforms to expected structure for + MEDM version ids. + """ + if not version_id.startswith(f"urn:medm:{cls.MEDM_ENTITY}:"): + return False # does not have correct header + parts = version_id.split(":") + if len(parts) != 8: + return False # does not have correct segments + if parts[-2] != "ver": + return False # does not follow version format + # NOTE: ignoring "named" versions for now + try: + int(parts[-1]) + except ValueError: + return False # does not have a valid version number + return True + + @classmethod + def get_version_number(cls, version_id: str) -> int: + """Return version number based on id.""" + if not cls.is_version_id(version_id): + raise FlowError(f"Invalid version id provided: {version_id}") + # NOTE: since id passed validation, there should be no errors here + return int(version_id.split(":")[-1]) + + @classmethod + def get_version_id(cls, asset_id: str, version_number: int) -> str: + """Generate a version id based on inputs. + NOTE: resultant version id will be structurally valid, but + may not actually exist. + + Args: + asset_id: MEDM asset id. + version_number: Number of version within asset. + + Returns: + MEDM version id. + """ + if not FlowAsset.is_asset_id(asset_id): + raise FlowError(f"Invalid asset id provided: {asset_id}") + + # Remove asset header in id + asset_id = asset_id.split(":", maxsplit=3)[-1] + + return f"urn:medm:{cls.MEDM_ENTITY}:{asset_id}:ver:{version_number}" + + @trace + def __init__(self, version: str | medm_model.NumberedAssetVersion): + """ + Args: + version: Either an MEDM version id, or an medm_model.NumberedAssetVersion object. + + Raises: + EntityNotFoundError + FlowError + """ + logger = get_logger(__name__) + + if isinstance(version, str): + if not self.is_version_id(version): + msg = f"Invalid version id provided: {version}" + raise FlowError(msg) + client = get_client() + q_input = medm_model.AssetVersionsByIdsInput(ids=[version]) + q_version = client.service_asset.asset_versions_by_ids(q_input) + try: + q_version.call() + except GQLAPIError as exc: + msg = f"Error querying version: {version}. {exc}" + raise FlowError(msg) from exc + if len(q_version.versions) == 0: + msg = "Error retrieving MEDM version." + raise EntityNotFoundError(entity_id=version, details=msg) + version = q_version.versions[0] + msg = f"Queried version number {version.version_number}." + logger.info(msg) + elif not isinstance(version, medm_model.NumberedAssetVersion): + msg = "Version error: input provided is not an medm_model.NumberedAssetVersion or version id." + raise FlowError(msg) + + # The medm version that this class represents + # This should be a medm_model.NumberedAssetVersion + # Storing this object so we can access its properties as needed + self._version = version + + # Numbered versions have attached revision objects + # Take the opportunity to cache this since revisions are immutable + # and should only be queried once! + FlowRevision._revision_cache[self.revision.id] = self.revision + + @property + def id(self) -> str: + return self._version.id + + @property + def asset_id(self) -> str: + """Return asset id of version.""" + return self._version.asset_id + + @property + def version_number(self) -> int: + """Return number of version.""" + return self._version.version_number + + @property + def revision(self) -> FlowRevision: + """Return current revision associated with version. + The revision associated with a version can change over time. + The version object would need to be re-queried to see the changes. + """ + return FlowRevision(self._version.revision) + + @property + def created_at(self) -> datetime.datetime: + """Creation timestamp of this version.""" + return datetime.datetime.fromisoformat( + self._version.created.date.replace("Z", "+00:00") + ) + + @property + def created_by(self) -> str | None: + """Username of creator of this version.""" + if self._version.created.user: + return self._version.created.user.user_name + return None + + def __str__(self): + """Readable string representation of version object.""" + s = "------------------------------\n" + s += f"VERSION: v{self.version_number}\n" + s += "------------------------------\n" + s += f" id: {self.id}\n" + s += f" revision: {self.revision.name} (r{self.revision.revision_number})\n" + return s + + +class FlowComponent: + """Container class for data relevant to a particular medm_model.Component. + Presents component data in a more readable and accessible way. + """ + + @trace + def __init__(self, revision: FlowRevision, component: medm_model.Component): + """ + Args: + revision: Parent FlowRevision object. + component: Medm component object to convert to custom object. + + Raises: + FlowError + """ + # FlowRevision object that this component belongs to + self.revision = revision + # Original MEDM component object + self._component = component + # Uniquely identifying component name + self.name = component.name + # Type id specifies the type of the component (e.g. binary, type, etc) + self.type_id = component.type_id + # Ancestors of type id + self.parent_type_ids = component.parent_type_ids + # The purpose further specifies what kind of component this is + # For instance two binary components can have different purposes + self.purpose = component.data.get("purpose", "") + # For binary components, the blob array specifies the files associated + # with the component + self.blobs = [] + # Arbitrary additional properties stored on the component + self.properties = {} # key = property name, value = property value + + blob_data = component.data.get("data", []) + for blob in blob_data: + self.blobs.append(Blob(blob)) + + for prop, val in component.data.items(): + if prop in ["data", "purpose"]: + continue # already processed these + self.properties[prop] = val + + @trace + def get_blob_path(self, blob_index: int = 0) -> str: + """Return the relative path stored on the blob at the given index + of the given component. + + Args: + blob_index: The index into the blob array to be inspected. + + Returns: + The path of the blob relative to the asset revision folder. + + Raises: + FlowError + """ + if len(self.blobs) <= blob_index: + msg = f'Component "{self.name}" does not have a blob at index {blob_index}.' + raise FlowError(msg) + return self.blobs[blob_index].path + + @trace + def fetch_blob_urls(self) -> list[str]: + """Query list of urls for component blobs that can be used for + downloading. Order should be preserved. + + Raises: + FlowError + """ + # Get the URNs that we need to fetch + urns = [blob.uri for blob in self.blobs] + + # Get info about current project + project_id = FlowProject.get_project_id(self.revision.id) + + return fetch_blob_urls(project_id, urns) + + @trace + def download( + self, + directory: str, + file_sequence: bool = False, + skip_download: bool = False, + ) -> dict[int, str]: + """Download all binary blobs in component to given directory. + Directory must exist, and component must be a binary component. + + Args: + directory: Existing folder location to be downloaded to. + file_sequence: If True, expect the component to contain a + zipped file sequence, and automatically expand it. + skip_download: Only relevant for file sequences. Used when + the source zip file has already been downloaded, but + the files haven't been extracted. + + Returns: + Dictionary of blob index to full path of downloaded file. + + Raises: + FlowError + """ + # Get info about current project + project_id = FlowProject.get_project_id(self.revision.id) + + # Do download + return download( + self._component, + project_id, + directory, + file_sequence, + skip_download, + ) + + def __str__(self): + """Readable string representation of component object.""" + s = "------------------------------\n" + s += f"COMPONENT: {self.name}\n" + s += "------------------------------\n" + s += f" type_id: {self.type_id}\n" + if self.purpose: + s += f" purpose: {self.purpose}\n" + return s + + +class Blob: + """Container class for data relevant to a blob within a component.""" + + @trace + def __init__(self, blob_data: dict): + """ + Args: + blob_data: A dictionary of blob information from an medm component. + + Raises: + FlowError + """ + try: + self.path = blob_data["path"] + self.uri = blob_data["uri"] + except KeyError as exc: + msg = "Invalid blob data dictionary provided." + raise FlowError(msg, details=str(exc)) from exc + + def __str__(self): + """Readable string representation of blob object.""" + return f"[BLOB: {self.path}]" diff --git a/python/tk-core/python/tank_vendor/flow_integration_sdk/publish.py b/python/tk-core/python/tank_vendor/flow_integration_sdk/publish.py new file mode 100644 index 00000000..4e822463 --- /dev/null +++ b/python/tk-core/python/tank_vendor/flow_integration_sdk/publish.py @@ -0,0 +1,684 @@ +# - +# ***************************************************************************** +# Copyright 2026 Autodesk, Inc. All rights reserved. +# +# These coded instructions, statements, and computer programs contain +# unpublished proprietary information written by Autodesk, Inc. and are +# protected by Federal copyright law. They may not be disclosed to third +# parties or copied or duplicated in any form, in whole or in part, without +# the prior written consent of Autodesk, Inc. +# ***************************************************************************** +# + + +""" +This module provides asset publishing utilities. +""" + +from __future__ import annotations # needed for python 3.9 support + +import os +import uuid +import shutil +import tempfile +import zipfile +from abc import ABC, abstractmethod +from dataclasses import dataclass + +from tank_vendor.flow_data_sdk.base import model as medm_model +from tank_vendor.flow_data_sdk.base.exceptions import GQLAPIError + +from . import transferapi +from .exceptions import ( + CreateAssetError, + ComponentSpecError, + FlowError, + PublishAssetError, +) +from .globals import ( + BASE_TYPE_ID, + BINARY_TYPE_ID, + COMMENT_COMP, + COMMENT_TYPE_ID, + DER_SOURCE_COMP, + DER_SOURCE_TYPE_ID, + get_client, + IMAGE_TYPE_ID, + SOURCE_COMP, + SOURCE_PURPOSE, + THUMBNAIL_COMP, + THUMBNAIL_PURPOSE, + TYPE_COMP, +) +from .schema import is_sub_type +from .storage import ( + _cache_asset_info, + _find_component, + get_storage_revision_dir, +) +from .utils import cleanpath, get_logger, mimetype, trace + + +@dataclass +class UploadBlob: + """Data class containing relevant information for blob transfer.""" + + #: Local uri assigned to blob. + uri: str + #: Full path to blob file in sandbox. + full_path: str + #: Path of blob file relative to draft directory. + blob_path: str + + +class ComponentSpec(ABC): + """Abstract base class for component specification objects. + This should be subclassed. + """ + + @property + def name(self) -> str: + """Return name of component. + (Should be uniquely identifying within a revision.) + """ + raise NotImplementedError() + + @abstractmethod + def create(self) -> medm_model.Component: + """Create an MEDM component based on specifications.""" + raise NotImplementedError() + + @staticmethod + @trace + def create_component( + name: str, type_id: str, **data + ) -> medm_model.ComponentDataInput: + """Generate a medm_model.Component object. + + Args: + name: Name of component. This must be unique within a revision. + **data: The remaining keyword args will be treated as additional properties + to be set on the component, for example, purpose. + + Returns: + Component object that can be added to an AssetRevision. + """ + return medm_model.ComponentDataInput(name=name, data=data, type_id=type_id) + + +class BinaryComponentSpec(ComponentSpec): + """Base class for all binary component specs.""" + + def __init__(self): + # List of blob upload information stored as UploadBlob objects + # At creation time, the medm component will be given a uuid per blob. + # The ids along with the associated file paths on disk are tracked in + # UploadBlob data structures to be used later on for blob uploading. + self._upload_blobs = [] + + def create( + self, + name: str, + files: list[str], + type_id: str = "", + purpose: str = "", + **properties, + ) -> medm_model.Component: + """Create a medm binary component that is ready for upload. + + Args: + name: Name of component. If the component name already exists, an exception + will be thrown unless "overwrite" is True in which case, the existing + component will be replaced. + files: Full paths to existing files to be added to component. + (Order will be preserved.) + type_id: Type of binary component to be created. If not specified, the base + binary type will be used. + purpose: A string constant, describing the general purpose of the component. + **properties: The remaining keyword args will be treated as additional properties + to be set on the binary component, for example, resolution + information for a thumbnail. + + Returns: + Binary component created. + + Raises: + ComponentSpecError + """ + # Check that the files are valid + for file in files: + if not os.path.exists(file): + msg = f"Invalid source path provided for binary component: {file}" + raise ComponentSpecError(details=msg) + + # Check that the type is valid + if type_id and not is_sub_type(BINARY_TYPE_ID, type_id): + msg = f"Type id {type_id} is not a binary component type." + raise ComponentSpecError(details=msg) + type_id = type_id or BINARY_TYPE_ID + + # NOTE: TEMPORARY SOLUTION! + # Due to a limitation in MEDM where only a max of 10 blobs + # can be stored per binary component, we must zip multiple files + # and upload as a single blob. + if len(files) > 1: + # Generate the zip in the same directory as the first file + base_dir = os.path.dirname(files[0]) + # At this point we can assume the file sequence has been vetted + # for the correct naming convention - parse the base name out of + # the first file to use as the zip file name. + base_name = os.path.basename(files[0]).split(".")[0] + zip_path = cleanpath(base_dir, f"{base_name}.zip") + with zipfile.ZipFile(zip_path, "w") as zip_file: + # Add each file to zip with only its base name + # and with lossless compression + for f in files: + zip_file.write( + f, + arcname=os.path.basename(f), + compress_type=zipfile.ZIP_DEFLATED, + ) + files = [zip_path] + + blobs = [] # blob list + self._upload_blobs = [] + for i, file in enumerate(files): + # Associate each blob with a unique id which will be + # used for uploading later + uri = f"upload://{uuid.uuid4()}" + blob_path = os.path.basename(file) + # Add blob info for medm component + blobs.append( + { + "uri": uri, + "path": blob_path, + "mimeType": mimetype(file), + "size": os.path.getsize(file), + } + ) + # Store upload information for each blob for later use when publishing + self._upload_blobs.append( + UploadBlob( + uri=uri, + full_path=file, + blob_path=blob_path, + ) + ) + + data = {} # data block + for prop, val in properties.items(): + data[prop] = val + data["data"] = blobs + data["purpose"] = purpose + + # Create new component + return self.create_component(name=name, type_id=type_id, **data) + + +class CommentComponentSpec(ComponentSpec): + """Specifications for creating a comment component. + This is a component used to store the publish comment of a revision. + There is only expected to be one of these per revision. + """ + + def __init__(self, comment: str): + """ + Args: + comment: Description of revision provided at publish time. + """ + self.comment = comment + + @property + def name(self) -> str: + return COMMENT_COMP + + def create(self) -> medm_model.Component: + """Create an MEDM component based on specifications.""" + + # NOTE: comment components have an optional "body" property that we are not + # leveraging for now... + return self.create_component( + name=self.name, + type_id=COMMENT_TYPE_ID, + subjectLine=self.comment, + ) + + +class DerivativeSourceComponentSpec(ComponentSpec): + """Specifications for creating a derivative source component. + This is a component used in a derivative asset to point back to its source asset. + There is only expected to be one of these per revision. + """ + + def __init__(self, revision_id: str): + """ + Args: + revision_id: Id of source revision. + """ + self.revision_id = revision_id + + @property + def name(self) -> str: + return DER_SOURCE_COMP + + def create(self) -> medm_model.Component: + """Create an MEDM component based on specifications.""" + return self.create_component( + name=self.name, + type_id=DER_SOURCE_TYPE_ID, + folder={"objectId": self.revision_id}, + ) + + +class SourceComponentSpec(BinaryComponentSpec): + """Specifications for creating a source component. + This is a component used to store the main source file(s) of the revision. + There is only expected to be one of these per revision. + """ + + def __init__(self, *files): + """ + Args: + files: List of files to be stored in the component. + """ + self.files = files + + @property + def name(self) -> str: + return SOURCE_COMP + + @trace + def create(self) -> medm_model.Component: + """Create an MEDM component based on specifications.""" + return super().create(name=self.name, files=self.files, purpose=SOURCE_PURPOSE) + + +class ThumbnailComponentSpec(BinaryComponentSpec): + """Specifications for creating a thumbnail component. + This is a component used to store the thumbnail for the revision. + There is only expected to be one of these per revision. + """ + + def __init__(self, thumbnail_file: str): + """ + Args: + thumbnail_file: Path to thumbnail file. + """ + self.file = thumbnail_file + + @property + def name(self) -> str: + return THUMBNAIL_COMP + + @trace + def create(self) -> medm_model.Component: + """Create an MEDM component based on specifications.""" + return super().create( + name=self.name, + files=[self.file], + purpose=THUMBNAIL_PURPOSE, + type_id=IMAGE_TYPE_ID, + ) + + +class TypeComponentSpec(ComponentSpec): + """Specifications for creating a type component. + This is a component used to provide a type designation for the revision. + More than one type component may be added to a revision. + + .. note:: Designating a "type" for an asset in MEDM involves adding a "type" component + to a revision. There is no restriction on the number of type components + on a revision, and no requirement that types must remain the same from one revision + to the next. Therefore, maintaining a consistent type is largely up to the pipeline. + """ + + def __init__(self, type_id: str = "", name: str = ""): + """ + Args: + type_id: The MEDM type identifier for the type component. + If not provided, the base type id will be used. + name: A unique name for the type component + (since there may be more than one). + If not provided, a default name will be used. + + Raises: + ComponentSpecError + """ + # If provided, type id must be subtype of base type + if type_id and not is_sub_type(BASE_TYPE_ID, type_id): + msg = f"Type id {type_id} is not a subclass of base type." + raise ComponentSpecError(details=msg) + + self.type_id = type_id or BASE_TYPE_ID + self._name = name or TYPE_COMP + + @property + def name(self) -> str: + return self._name + + def create(self) -> medm_model.Component: + """Create an MEDM component based on specifications.""" + return self.create_component( + name=self.name, + type_id=self.type_id, + ) + + +class FileSeqComponentSpec(TypeComponentSpec): + """Specifications for creating a file sequence type component. + This is a component used to designate an asset as containing a file sequence. + This may be combined with other type designations to describe the nature of the asset. + """ + + def __init__( + self, + type_id: str, + frame_start: int, + frame_end: int, + frame_set: str, + file_format: str, + name: str = "", + ): + """ + Args: + type_id: The MEDM type identifier for the type component. + frame_start: First frame of file sequence. + frame_end: End frame of file sequence. + frame_set: A string expression denoting the set of frames + within the sequence. (e.g. "1-5,10,13-20") + file_format: A string expression denoting the file naming and + frame padding convention of the sequence. + (e.g. "render.%04d.exr") + name: A unique name for the type component + (since there may be more than one type component). + If not provided, a default name will be used. + + Raises: + ComponentSpecError + """ + # NOTE: for now, this type id will be passed in because it will + # be a custom schema. Later when the file sequence type id + # is formalized under autodesk namespace, we will create a + # global constant and use that instead. + + # If provided, type id must be subtype of base type + if not is_sub_type(BASE_TYPE_ID, type_id): + msg = f"Type id {type_id} is not a subclass of base type." + raise ComponentSpecError(details=msg) + + self.type_id = type_id + self.frame_start = frame_start + self.frame_end = frame_end + self.frame_set = frame_set + self.file_format = file_format + self._name = name or TYPE_COMP + + @property + def name(self) -> str: + return self._name + + @trace + def create(self) -> medm_model.Component: + """Create an MEDM component based on specifications.""" + return self.create_component( + name=self.name, + type_id=self.type_id, + frameStart=self.frame_start, + frameEnd=self.frame_end, + frameSet=self.frame_set, + fileFormat=self.file_format, + ) + + +@trace +def publish_new_asset( + name: str, + parent_id: str, + description: str = "", + components: list[ComponentSpec] | None = None, + used_versions: list[str] | None = None, +) -> medm_model.Asset: + """Create a new asset in AM. + + Args: + name: Name of asset. + parent: Entity under which to create the asset. + This should be an Asset or a Project object. + This can also be the id of an asset or project. + description: Optional description of asset. + components: List of component specifications that will be converted into components + to be attached to asset revision. + (Components are used to store binaries and metadata on revisions.) + used_versions: List of version ids of other assets used by this asset. + (Stored as "uses" relationships with other asset versions.) + + Raises: + CreateAssetError + """ + # Prepare medm component objects for publish + medm_components = _generate_medm_components(components) + + # Prepare medm uses inputs for publish + medm_uses = _generate_medm_uses(used_versions) + + # Call create asset mutation + client = get_client() + m_input = medm_model.CreateAssetInput( + name=name, + parent_id=parent_id, + components=medm_components, + description=description, + uses=medm_uses, + ) + m_create = client.service_asset.create_asset(m_input) + try: + m_create.call() + except GQLAPIError as exc: + msg = f'Error creating asset "{name}" on remote. {exc}. Request id: {exc.request_id}' + raise CreateAssetError(details=msg) from exc + + # Get new published asset + asset = m_create.asset + + # Upload associated binaries + if components is not None: + bin_specs = [ + spec for spec in components if isinstance(spec, BinaryComponentSpec) + ] + if not bin_specs: + return asset # nothing to transfer + _upload_binaries(asset, bin_specs) + + return asset + + +@trace +def publish_new_revision( + asset_id: str, + components: list[ComponentSpec] | None = None, + used_versions: list[str] | None = None, +) -> medm_model.Asset: + """Publish a new version of an existing asset. + + Args: + asset_id: Medm id of asset to be published. + components: List of component specifications that will be converted into components + to be attached to asset revision. + (Components are used to store binaries and metadata on revisions.) + used_versions: List of version ids of other assets used by this asset. + (Stored as "uses" relationships with other asset versions.) + + Returns: + Updated asset object. + NOTE: input asset object should be deprecated after publish. + + Raises: + PublishAssetError + """ + # Prepare medm component objects for publish + medm_components = _generate_medm_components(components) + + # Prepare medm uses inputs for publish + medm_uses = _generate_medm_uses(used_versions) + + client = get_client() + m_input = medm_model.UpdateAssetInput( + id=asset_id, + components=medm_components, + components_action=medm_model.ListAction.REPLACE.value, + named_version_change=medm_model.NamedVersionChangeEnum.CREATE_NEW.value, + uses=medm_uses, + ) + + m_update = client.service_asset.update_asset(m_input) + try: + m_update.call() + except GQLAPIError as exc: + msg = f"Failed to update asset. {exc}" + raise PublishAssetError(details=msg) from exc + + # Get updated asset + asset = m_update.asset + + # Upload associated binaries + if components is not None: + bin_specs = [ + spec for spec in components if isinstance(spec, BinaryComponentSpec) + ] + if not bin_specs: + return asset # nothing to transfer + _upload_binaries(asset, bin_specs) + + return asset + + +def _generate_medm_components( + comp_specs: list[ComponentSpec], +) -> medm_model.ComponentDataInput: + """Given component specs create medm component objects to be used in a publish.""" + comp_specs = [] if comp_specs is None else comp_specs + medm_components = [comp.create() for comp in comp_specs] + return medm_components + + +def _generate_medm_uses(used_versions: list[str]) -> medm_model.UsesTargetInput: + """Convert a list of version ids into a list of UsesTargetInput objects.""" + used_versions = [] if used_versions is None else used_versions + uses_inputs = [] + for version_id in used_versions: + uses_inputs.append(medm_model.UsesTargetInput(to_version_id=version_id)) + return uses_inputs + + +@trace +def _upload_binaries(asset: medm_model.Asset, bin_specs: list[BinaryComponentSpec]): + """Upload binaries of an asset post publish. + + Args: + bin_specs: List of BinaryComponentSpecs which indicate where + to find files to be uploaded. These should correspond + to existing binary components on asset. + """ + logger = get_logger(__name__) + + # Now upload the blobs to remote storage + # Before doing so, copy all binary data into a temporary location + # to insulate the transfer from external changes. + + logger.info("Copying binary data to temporary location for transfer...") + with tempfile.TemporaryDirectory() as tmpdir: + for bin_spec in bin_specs: + for upload_blob in bin_spec._upload_blobs: + temp_path = cleanpath(tmpdir, upload_blob.blob_path) + try: + shutil.copyfile(upload_blob.full_path, temp_path) + except Exception as exc: # pylint: disable=broad-except + msg = f'Failed to copy "{upload_blob.full_path}" to temp location "{temp_path}". {exc}' + raise PublishAssetError(details=msg) from exc + upload_blob.full_path = temp_path + + # Do file transfer to both remote and primary storage + _transfer_files(asset, bin_specs) + + +@trace +def _transfer_files(asset: medm_model.Asset, bin_specs: list[BinaryComponentSpec]): + """Transfer files associated with binary components to both remote + storage and primary storage. The medm asset provided is expected to + have been published to MEDM already. The binary components provided are expected + to have been relocated to a temporary location for transfer. + + Raises: + PublishAssetError + """ + logger = get_logger(__name__) + logger.info("Transfering binary data to cloud...") + + # The API will have mutated the component objects on the medm asset now, + # and their uri attributes will have been replaced with a URN which can be used + # to upload the blobs to. + + # NOTE: this whole process sucks! It's possible that the asset will be published, + # but the binary data transfer will fail, in which case you have a half published + # asset. This is really a flaw in the way the API works - you should upload the + # data first and then publish if the data transfer succeeds! + + # The transfer API uses the raw graphql connection + gql_client = get_client() + + for bin_spec in bin_specs: + # Find matching component represented by the component spec object + comp = _find_component(asset, name=bin_spec.name) + if not comp: + msg = f'Component "{bin_spec.name}" missing on medm asset object.' + raise PublishAssetError(details=msg) + + for i, upload_blob in enumerate(bin_spec._upload_blobs): + comp_blobs = comp.data.get("data", []) + if i >= len(comp_blobs): + msg = f'Component "{comp.name}" missing blob {i}.' + raise PublishAssetError(details=msg) + blob_urn = comp_blobs[i]["uri"] + + logger.info(f"Uploading {upload_blob.full_path}...") + + transferapi.upload_blob( + client=gql_client, + file_path=upload_blob.full_path, + urn_id=blob_urn, + upload_uri=upload_blob.uri, + ) + + logger.info(f"File {upload_blob.full_path} uploaded successfully!") + + # Copy files to blob storage as well (create folder if necessary) + logger.info("Transfering binary data to local storage...") + # Storage directory for latest version of asset + storage_dir = get_storage_revision_dir(asset.id, asset.revision_number) + if not os.path.exists(storage_dir): + os.makedirs(storage_dir) + has_failures = False + for bin_spec in bin_specs: + for upload_blob in bin_spec._upload_blobs: + source_path = cleanpath(upload_blob.full_path) + storage_path = cleanpath(storage_dir, upload_blob.blob_path) + logger.info(f"Copying {source_path} -> {storage_path}") + try: + shutil.copyfile(source_path, storage_path) + except Exception as exc: # pylint: disable=broad-except + # Since this copy is not publish critical, we will not stop execution + # if it fails + logger.warning(f"Copy to primary storage failed: {exc}") + has_failures = True + + # Add sidecar file to cache important asset data if necessary + try: + _cache_asset_info(asset.id) + except FlowError as exc: + logger.warning(f"Caching asset info in storage dir failed: {exc}") + has_failures = True + + if has_failures: + logger.info("All critical transfers complete!") + logger.warning("Some non-critical transfers/processes failed.") + else: + logger.info("All transfers complete!") diff --git a/python/tk-core/python/tank_vendor/flow_integration_sdk/sandbox.py b/python/tk-core/python/tank_vendor/flow_integration_sdk/sandbox.py new file mode 100644 index 00000000..673fce99 --- /dev/null +++ b/python/tk-core/python/tank_vendor/flow_integration_sdk/sandbox.py @@ -0,0 +1,794 @@ +# - +# ***************************************************************************** +# Copyright 2026 Autodesk, Inc. All rights reserved. +# +# These coded instructions, statements, and computer programs contain +# unpublished proprietary information written by Autodesk, Inc. and are +# protected by Federal copyright law. They may not be disclosed to third +# parties or copied or duplicated in any form, in whole or in part, without +# the prior written consent of Autodesk, Inc. +# ***************************************************************************** +# + + +""" +This module contains classes and utilities that facilitate the "sandbox". +This is a space on disk reserved for work in progress. Assets can be created, +or checked out in the sandbox, modified, and then published back to MEDM. + +The collection of files for an asset that is in sandbox is called a "draft". +These are contained within a "draft folder" whose location is managed by this module +(though the root location is configured in `storage.set_sandbox_root()`). +Within, the draft folder, a draft info sidecar file is stored to record important +information about a new draft or checkout draft. + * new draft = working on brand new asset + * checkout draft = working on published asset + +Draft files are by definition unpublished. Drafts are identified by either +an asset's storage key or temporary uuid for brand new assets. + +Within this module, we have utilities for querying draft information from the file system. +As well, there are key utilities for manipulating drafts including + * creating new assets as drafts + * checking out assets as drafts + * discarding drafts + * publishing new assets or revisions from drafts +""" + +from __future__ import annotations # needed for python 3.9 support + +import json +import os +import shutil +import uuid +from dataclasses import dataclass, asdict + +from tank_vendor.flow_data_sdk.base import model as medm_model +from tank_vendor.flow_data_sdk.base.exceptions import GQLAPIError + +from .fetch import fetch +from .globals import BASE_TYPE_ID, get_client +from .exceptions import ( + EntityNotFoundError, + FlowError, + DraftExistsError, + InvalidDraftError, + PublishAssetError, + PublishConflictError, +) +from .publish import ( + CommentComponentSpec, + ComponentSpec, + publish_new_asset, + publish_new_revision, + TypeComponentSpec, +) +from .storage import ( + get_sandbox_root, + get_storage_component_path, + get_storage_key, +) +from .utils import ( + cleanpath, + get_logger, + is_sub_directory, + relpath, + trace, +) + + +@dataclass +class DraftInfo: + """Base class for draft info dataclasses with providing common functionality.""" + + #: Id that uniquely identifies a draft in local sandbox. + draft_id: str + + @trace + def write_file(self, file_path: str): + """Write contents of dataclass to draft info file. + + Args: + file_path: Full path to destination file. + + Raises: + FlowError + """ + try: + with open(file_path, "w") as f: + f.write(json.dumps(asdict(self), indent=4)) + except Exception as exc: # pylint: disable=broad-except + msg = f"Draft info file could not be written: {file_path}" + raise FlowError(msg) from exc + + @classmethod + @trace + def read_file(cls, file_path: str) -> DraftInfo: + """Read a draft info file and convert into draft dataclass. + + Args: + file_path: Full path to draft info file. + + Returns: + Sub-class of BaseDraft. + + Raises: + FlowError + """ + try: + with open(file_path, "r") as f: + json_str = f.read() + except (IOError, OSError) as exc: + msg = f"Draft info file could not be opened: {file_path}" + raise FlowError(msg) from exc + + try: + json_dict = json.loads(json_str) + except json.JSONDecodeError as exc: + msg = f"Draft info file format is invalid: {file_path}" + raise FlowError(msg) from exc + + draft_type = json_dict.get("draft_type") + if draft_type == "new": + try: + return NewDraftInfo(**json_dict) + except TypeError as exc: + msg = f"Draft info file is missing required attributes for NewDraftInfo: {file_path}" + raise FlowError(msg) from exc + elif draft_type == "checkout": + try: + return CheckoutDraftInfo(**json_dict) + except TypeError as exc: + msg = f"Draft info file is missing required attributes for CheckoutDraftInfo: {file_path}" + raise FlowError(msg) from exc + else: + msg = f"Draft info file has invalid draft type designated: {file_path}" + raise FlowError(msg) + + def pprint(self): + """Print dataclass contents in readable way.""" + + logger = get_logger(__name__) + printstr = "\n-----------------------------------------\n" + printstr += f"{self.__class__.__name__}\n" + printstr += "-----------------------------------------\n" + for prop, value in self.__dict__.items(): + printstr += f" {prop}: {value}\n" + logger.info(printstr) + + +@dataclass +class CheckoutDraftInfo(DraftInfo): + """Container for info associated with a checkout draft. + NOTE: A checkout draft is a draft that results from checking out + a published revision. + """ + + #: Name of asset. + name: str + #: Id of asset. + asset_id: str + #: Version number of asset checked out. + version: int + #: Revision number of asset checked out. + revision: int + #: Latest published version of asset at the time of checkout. + latest_version: int + #: Latest published revision of asset at the time of checkout. + latest_revision: int + #: Source path of draft. + source_path: str + #: Record draft type as a constant property which makes recording to file easier. + draft_type: str = "checkout" + + +@dataclass +class NewDraftInfo(DraftInfo): + """Container for info associated with a new draft. + NOTE: A new draft is a draft for an asset that has been created and not yet published. + """ + + #: Name of asset. + name: str + #: Description of new asset. + description: str + #: Id of parent of new asset. + parent_id: str + #: Type ids to be associated with new asset. + type_ids: list[str] + #: Source path of draft. + source_path: str + #: Record draft type as a constant property which makes recording to file easier. + draft_type: str = "new" + + +# ------------------------------------------ +# DRAFT DATA ACCESSORS +# ------------------------------------------ + + +@trace +def get_draft_id(asset_id: str | None = None) -> str: + """Return a local draft id for given asset. + + Args: + asset_id: Medm asset id. If None, this is treated as a new asset - + return a temporary uuid. + + Returns: + Draft id for asset. + """ + # NOTE: For this sandbox implementation, we will use the storage key as the + # draft id for any published assets, and generate a uuid for new asset. + if asset_id is None: + return str(uuid.uuid4()) + else: + return get_storage_key(asset_id) + + +def get_draft_folder(draft_id: str) -> str: + """Return the location of the draft folder for the given draft id. + + Args: + draft_id: Id that uniquely identifies a draft within local sandbox. + + Returns: + Full path to draft folder on local disk. + """ + sandbox_root = get_sandbox_root() + return f"{sandbox_root}/{draft_id}/draft" + + +def get_draft_info_file(draft_id: str) -> str: + """Return expected path to draft info sidecar file, whether + or not it exists. + + Args: + draft_id: Id that uniquely identifies a draft within local sandbox. + + Returns: + Full path to draft info sidecar file on local disk. + """ + return cleanpath(get_draft_folder(draft_id), ".draft") + + +@trace +def read_draft_info(draft_id: str) -> DraftInfo: + """Read the draft info sidecar file and convert into draft dataclass. + + Args: + draft_id: Id that uniquely identifies a draft within local sandbox. + + Returns: + Subclass of DraftInfo dataclass. + + Raises: + InvalidDraftError + """ + draft_info_file = get_draft_info_file(draft_id) + if not os.path.exists(draft_info_file): + msg = f"Draft info file not found: {draft_info_file}" + raise InvalidDraftError(draft_id=draft_id, details=msg) + try: + return DraftInfo.read_file(draft_info_file) + except FlowError as exc: + raise InvalidDraftError(draft_id=draft_id, details=str(exc)) + + +@trace +def is_new_asset(draft_id: str) -> bool: + """Return True if the draft id provided points to a new asset that has + never been published. + + Args: + draft_id: Id that uniquely identifies a draft within local sandbox. + """ + draft_info = read_draft_info(draft_id) + return isinstance(draft_info, NewDraftInfo) + + +@trace +def is_local_draft(draft_id: str) -> bool: + """Return True if the draft id provided points to draft in local sandbox. + + Args: + draft_id: Id that uniquely identifies a draft within local sandbox. + """ + draft_folder = get_draft_folder(draft_id) + return os.path.exists(draft_folder) + + +@trace +def get_draft_context(draft_path: str) -> str | None: + """Given a file path, if it is a draft path in the local sandbox, + return the associated draft id. Otherwise return None. + + Args: + draft_path: A file path which is potentially belonging to a draft. + + Returns: + Draft id or None if path does not belong to a draft. + + Raises: + ValueError + """ + sandbox_root = get_sandbox_root() + if not is_sub_directory(sandbox_root, draft_path): + return None + + draft_path = relpath(sandbox_root, draft_path) + try: + draft_id, version, filename = draft_path.split("/") + except ValueError: + return None + + if version != "draft": + return None + + return draft_id + + +def get_asset_drafts(asset_id: str) -> list[CheckoutDraftInfo]: + """Return all local drafts that exist for the given asset. + + .. note:: Currently, we only support a single draft per asset, + however this could change in the future with the introduction + of multiple checkouts/sandboxes. Returning a list keeps this + utility flexible. + + The returned values will be CheckoutDraftInfo objects which will contain + detailed information about each draft. + + .. note:: This function can only be used to retrieve information about + checkouts. Please use `get_drafts(draft_type="new")` function + to retrieve list of brand new assets that exist in sandbox. + + Args: + asset_id: Id of MEDM asset. This can also be a revision or version id + from the same asset. + + Returns: + List of CheckoutDraftInfo objects. + """ + # Retrieve asset's draft id + draft_id = get_draft_id(asset_id) + # Read draft info if it exists + try: + draft_info = read_draft_info(draft_id) + except InvalidDraftError: + return [] + return [draft_info] + + +def get_drafts(draft_type: str | None = None) -> list[DraftInfo]: + """Return a list of all drafts found in local sandbox. + + Args: + draft_type: If specified, filter by given draft type. + Accepted values include "new" or "checkout". + + Returns: + List of DraftInfo objects that may be either + CheckoutDraftInfo or NewDraftInfo types. + + Raise: + FlowError + """ + sandbox_root = get_sandbox_root() + if not os.path.exists(sandbox_root): + raise FlowError(f"Configured sandbox root does not exist: {sandbox_root}") + + drafts = [] + for item in os.listdir(sandbox_root): + if not os.path.isdir(os.path.join(sandbox_root, item)): + continue + draft_id = item + try: + draft_info = read_draft_info(draft_id) + except InvalidDraftError: + continue + if draft_type is not None and draft_type != draft_info.draft_type: + continue # skip if filter doesn't match + drafts.append(draft_info) + return drafts + + +# ------------------------------------------ +# SANDBOX UTILITIES +# ------------------------------------------ + + +@trace +def create_asset_in_sandbox( + name: str, + description: str, + parent_id: str, + type_ids: list[str] | None = None, + source_path: str = "", +) -> NewDraftInfo: + """Create a workspace for a new asset in sandbox. + + Args: + name: String name for asset. + description: Description of the asset. + parent_id: Id of parent entity, which may be a project, folder or asset. + type_ids: List of type ids to be attached to asset. + source_path: Optionally provide a source file to be associated with new asset. + + Returns: + NewDraftInfo object. + + Raises: + EntityNotFoundError + """ + + # NOTE: This function will "create" an asset in the sense of creating a + # draft location for the asset and storing a sidecar file with + # relevant metadata, but not create anything on MEDM. + # The actual creation of the MEDM asset will be done at publish time. + + # Create unique draft id that is file safe + draft_id = get_draft_id() + + # Create a draft folder + draft_folder = get_draft_folder(draft_id) + if not os.path.exists(draft_folder): + os.makedirs(draft_folder) + + # Copy source file to draft folder if applicable + # TODO: This is over-simplified, and makes assumptions about a single source file + # as well as simple types. + # In future, we should accept ComponentSpecs instead which must + # be serialized into the draft info file to be re-created at publish time. + # We should also allow specification of multiple "input" files. + if source_path: + draft_path = cleanpath(draft_folder, os.path.basename(source_path)) + shutil.copyfile(source_path, draft_path) + else: + draft_path = "" + + # Add a sidecar file to store metadata of new asset + # This will ensure that when it's publish time, we have all the info we need + # to create the appropriate asset entity. + draft_info_file = get_draft_info_file(draft_id) + draft_info = NewDraftInfo( + draft_id=draft_id, + name=name, + description=description, + parent_id=parent_id, + type_ids=type_ids or [], + source_path=draft_path, + ) + draft_info.write_file(draft_info_file) + + return draft_info + + +@trace +def checkout_revision( + revision: medm_model.AssetRevision, + component_name: str = "", + component_purpose: str = "", + force: bool = False, +) -> CheckoutDraftInfo: + """Check out the given asset revision into sandbox. + If a draft already exists, an exception will be thrown unless force=True. + + Args: + revision: AssetRevision object. + component_name: If provided, search for component with this name to be checked out. + This should be unique within the revision. + component_purpose: If provided, search for a component with this purpose to be + checked out. There may be multiple components with the same purpose, + so the first match will be returned. + force: If True, force a re-checkout even if an existing draft is found. + + Returns: + A CheckoutDraftInfo object containing all pertinent information + of new or existing checkout. + + Raises: + DraftExistsError + FlowError + """ + logger = get_logger(__name__) + + draft_id = get_draft_id(revision.asset_id) + draft_folder = get_draft_folder(draft_id) + + if os.path.exists(draft_folder): + if not force: + raise DraftExistsError(draft_id=draft_id, draft_folder=draft_folder) + else: + logger.info("Removing existing draft folder...") + # Delete existing draft folder + try: + shutil.rmtree(draft_folder) + except Exception as exc: # pylint: disable=broad-except + msg = f"Could not remove existing draft directory: {draft_folder}" + raise FlowError(msg) from exc + + # Fetch necessary binaries for revision into primary storage + # TODO: Assumption that we are retrieving a single blob every time. + # Need to expand this to grab all blobs, or an explicit list. + fetch( + revision, + component_name=component_name, + component_purpose=component_purpose, + ) + + # Create the draft folder so we can copy new files to it + logger.info("Creating draft folder...") + os.makedirs(draft_folder) + + # Copy from storage into sandbox + # NOTE: assuming flat files in asset dir + # NOTE: assuming only a single source file + cached_path = get_storage_component_path( + revision, + component_name=component_name, + component_purpose=component_purpose, + ) + if cached_path is None: + # This indicates that the specified component does not exist + # on the revision, so raise an error + msg = "Revision does not contain a component fitting the filter: " + msg += f"component_name = {component_name}, component_purpose = {component_purpose}. " + msg += "No source file(s) to copy. Checkout cannot be completed." + raise FlowError(msg) + + draft_path = cleanpath(draft_folder, os.path.basename(cached_path)) + msg = "Copying files to draft folder...\n" + msg += f"{cached_path} -> {draft_path}" + logger.info(msg) + try: + shutil.copy(cached_path, draft_path) + except Exception as exc: # pylint: disable=broad-except + raise FlowError("Copy failed!") from exc + + # Query the asset to get it's latest info + asset = _query_asset(revision.asset_id) + + # Write draft info + draft_info_file = get_draft_info_file(draft_id) + draft_info = CheckoutDraftInfo( + draft_id=draft_id, + name=asset.name, + asset_id=asset.id, + revision=revision.revision_number, + latest_revision=asset.revision_number, + version=revision.version_number, + latest_version=asset.version_number, + source_path=draft_path, + ) + draft_info.write_file(draft_info_file) + logger.info("Draft metadata updated.") + + return draft_info + + +@trace +def discard_draft(draft_id: str): + """Discard an existing draft of an asset. + + Args: + draft_id: Unique id that identifies a draft location in local sandbox. + + Raises: + FlowError + InvalidDraftError + """ + logger = get_logger(__name__) + + if not is_local_draft(draft_id): + msg = f'The draft "{draft_id}" is not in local sandbox.' + raise InvalidDraftError(draft_id=draft_id, details=msg) + + draft_folder = get_draft_folder(draft_id) + parent_folder = os.path.dirname(draft_folder) + logger.info(f"Deleting draft folder: {parent_folder}...") + try: + # Remove the parent directory above "draft" folder + shutil.rmtree(parent_folder) + except Exception as exc: # pylint: disable=broad-except + msg = f"Draft folder could not deleted: {parent_folder}" + raise FlowError(msg) from exc + + +@trace +def publish_draft( + draft_id: str, + comment: str = "", + components: list[ComponentSpec] | None = None, + used_versions: list[str] | None = None, + force: bool = False, +) -> medm_model.Asset: + """Publish a new asset or new revision of existing asset from a draft. + + Args: + draft_id: Unique local draft id. + comment: Description of revision. + components: List of component specifications that will be converted into components + to be attached to asset revision. + (These are used to store binaries and metadata on revisions.) + used_versions: List of version ids of other assets used by this asset. + (Stored as "uses" relationships with other asset versions.) + force: If True, publish without checking for publish conflicts. + + Returns: + Updated MEDM asset object. + + Raises: + EntityNotFoundError + PublishAssetError + PublishConflictError + """ + logger = get_logger(__name__) + + # Retrieve draft metadata + draft_info = read_draft_info(draft_id) + + # Add comment component to components if not already present + components = [] if components is None else list(components) + components.append(CommentComponentSpec(comment)) + + if draft_info.draft_type == "new": + # For new assets, we need to convert the type ids into components + # (Names must be kept unique!) + for i, type_id in enumerate(draft_info.type_ids): + components.append(TypeComponentSpec(type_id=type_id, name=f"Type {i}")) + + # Publish new asset with appropriate metadata + asset = publish_new_asset( + name=draft_info.name, + parent_id=draft_info.parent_id, + description=draft_info.description, + components=components, + used_versions=used_versions, + ) + + # Rename the draft folder on first publish to replace the temporary draft + # id with the asset's permanent storage key + new_draft_id = get_storage_key(asset.id) + new_source_path = _rename_draft_folder(draft_id, new_draft_id) + draft_id = new_draft_id + + else: + # Query the asset + asset_id = draft_info.asset_id + try: + asset = _query_asset(asset_id) + except FlowError as exc: + msg = f"Invalid asset id associated with draft: {asset_id}" + raise EntityNotFoundError(entity_id=asset_id, details=msg) from exc + + # For existing assets, we need to preserve the types already assigned + # to the asset - add these to the component list + for i, type_id in enumerate(_get_type_ids(asset)): + components.append(TypeComponentSpec(type_id=type_id, name=f"Type {i}")) + + # Check for conflict before publishing + # A conflict arises if newer revisions of the asset have been published + # since this asset was checked out. + if not force and _check_publish_conflict(draft_info, asset): + checkout_revision = draft_info.revision + checkout_version = draft_info.version + raise PublishConflictError( + asset=asset, + checkout_version=checkout_version, + checkout_revision=checkout_revision, + ) + + # Update an existing asset by publishing a new revision + asset = publish_new_revision( + asset_id=asset_id, + components=components, + used_versions=used_versions, + ) + + # For publishes of existing assets, the source path should not change + new_source_path = draft_info.source_path + + msg = f'Successfully published version {asset.version_number} (r{asset.revision_number}) of asset "{asset.name}" to Flow AM!' + logger.info(msg) + + # Update the draft info file to adjust for latest publish. + # This is a sidecar file that will contain any relevant "checkout" information + # about current draft. After a publish, we will treat the draft has being + # checked out from the most recent published version. + # NOTE: we can continue to use the same draft id. + draft_info_file = get_draft_info_file(draft_id) + draft_info = CheckoutDraftInfo( + draft_id=draft_id, + name=asset.name, + asset_id=asset.id, + revision=asset.revision_number, + version=asset.version_number, + latest_revision=asset.revision_number, + latest_version=asset.version_number, + source_path=new_source_path, + ) + draft_info.write_file(draft_info_file) + logger.info("Draft metadata updated.") + + return asset + + +@trace +def _rename_draft_folder(old_draft_id: str, new_draft_id: str) -> str: + """Attempt to rename the draft folder to the new draft id. + If it's not possible, create the new draft folder and copy contents over. + + Returns: + Path of new path to source file. + + Raises: + PublishAssetError + """ + logger = get_logger(__name__) + logger.info(f'Renaming draft folder from "{old_draft_id}" -> "{new_draft_id}".') + draft_info = read_draft_info(old_draft_id) + old_draft_folder = os.path.dirname(get_draft_folder(old_draft_id)) + new_draft_folder = os.path.dirname(get_draft_folder(new_draft_id)) + try: + os.rename(old_draft_folder, new_draft_folder) + except Exception: # pylint: disable=broad-except + msg = f"Could not rename draft folder: {old_draft_folder}. " + msg += "Creating a copy instead..." + logger.warning(msg) + try: + shutil.copytree(old_draft_folder, new_draft_folder) + except Exception as exc: # pylint: disable=broad-except + msg = f"Could not create draft folder: {new_draft_folder}" + exc_data = {"draft_folder": old_draft_folder} + raise PublishAssetError(data=exc_data, details=msg) from exc + + # Determine new source path + old_source_path = draft_info.source_path + new_source_path = old_source_path.replace(old_draft_folder, new_draft_folder) + return new_source_path + + +def _query_asset(asset_id: str) -> medm_model.Asset: + """Query an asset based on id. + + Raises: + EntityNotFoundError + FlowError + """ + logger = get_logger(__name__) + client = get_client() + q_input = medm_model.AssetsByIdsInput(ids=[asset_id]) + q_asset = client.service_asset.assets_by_ids(q_input) + try: + q_asset.call() + except GQLAPIError as exc: + msg = f"Error querying asset: {asset_id}. {exc}" + raise FlowError(msg) from exc + if len(q_asset.assets) == 0: + msg = "Error retrieving MEDM asset." + raise EntityNotFoundError(entity_id=asset_id, details=msg) + asset = q_asset.assets[0] + logger.info(f'Queried asset "{asset.name}".') + return asset + + +def _get_type_ids(asset: medm_model.Asset): + """Return list of type ids assigned to asset.""" + type_ids = [] + for comp in asset.components: + if BASE_TYPE_ID in comp.parent_type_ids: + type_ids.append(comp.type_id) + return type_ids + + +def _check_publish_conflict(draft_info, asset) -> bool: + """Check if there is a publish conflict based on current state of + asset and checkout state of asset. + """ + # A conflict is defined as trying to publish an asset which has had + # new versions published against it since the asset was checked out + # + # NOTE: We are making the assumption that within a single "numbered" version, + # component and uses relationships stay the same from revision to revision! + # This assumption allows us to only consider conflicts with respect to versions. + return draft_info.latest_version < asset.version_number diff --git a/python/tk-core/python/tank_vendor/flow_integration_sdk/schema.py b/python/tk-core/python/tank_vendor/flow_integration_sdk/schema.py new file mode 100644 index 00000000..7675f120 --- /dev/null +++ b/python/tk-core/python/tank_vendor/flow_integration_sdk/schema.py @@ -0,0 +1,258 @@ +# - +# ***************************************************************************** +# Copyright 2026 Autodesk, Inc. All rights reserved. +# +# These coded instructions, statements, and computer programs contain +# unpublished proprietary information written by Autodesk, Inc. and are +# protected by Federal copyright law. They may not be disclosed to third +# parties or copied or duplicated in any form, in whole or in part, without +# the prior written consent of Autodesk, Inc. +# ***************************************************************************** +# + +"""Utilities for querying and caching schema information.""" + +from __future__ import annotations # needed for python 3.9 support + +import json +import os +from functools import cache + +from tank_vendor.flow_data_sdk.base import model as flow_model +from tank_vendor.flow_data_sdk.base.exceptions import GQLAPIError + +from .exceptions import FlowError +from .globals import ( + BASE_TYPE_ID, + BINARY_TYPE_ID, + COMMENT_TYPE_ID, + DER_SOURCE_TYPE_ID, + FOLDER_TYPE_ID, + get_client, + get_session_collection, + IMAGE_TYPE_ID, +) +from .utils import get_logger, trace + + +# Schema inheritance tree cache +# ----------------------------- +# Stored globally as an optimization to avoid querying/re-querying +# parent types of known schema types. +# Format: key = schema type id, value = list of parent types +_schema_tree: dict[str, list[str]] = {} + +# Hardcode some well known relationships and root types +_schema_tree[IMAGE_TYPE_ID] = [BINARY_TYPE_ID] +_schema_tree[FOLDER_TYPE_ID] = [BASE_TYPE_ID] +_schema_tree[BASE_TYPE_ID] = [] +_schema_tree[BINARY_TYPE_ID] = [] +_schema_tree[COMMENT_TYPE_ID] = [] +_schema_tree[DER_SOURCE_TYPE_ID] = [] + + +# Schema type ids cache +# --------------------- +# This maps custom type names to full type ids +# Format: key = type name (e.g. "type.maya.workfile"), value = full type id +_schema_ids: dict[str, str] = {} + + +# Schema display name cache +# ------------------------- +# Caches display data of schemas to avoid re-querying. +# Format: key = type id, value = display name +_schema_display_names: dict[str, str] = {} + + +def _read_schema_config(config_path: str): + """Read the given json config file, and return raw json dictionary.""" + logger = get_logger(__name__) + + logger.info(f"Reading schema type ids config file: {config_path}") + if not os.path.exists(config_path): + raise RuntimeError(f"Schema config file not found: {config_path}") + + with open(config_path) as f: + try: + raw_config = json.loads(f.read()) + except json.decoder.JSONDecodeError as exc: + raise ValueError("Schema config file is invalid.") from exc + return raw_config + + +def _compose_schema_id(type_name: str, version: str): + """Generate a full type id with info provided.""" + session_collection = get_session_collection() + org_id = session_collection.organization_id + group_id = session_collection.group_id + namespace = f"{org_id}.{group_id}" + return f"{namespace}:{type_name}-{version}" + + +@trace +def cache_schema_config(config_path: str): + """Add types from provided schema config json file into schema cache + to optimize queries against them. + + ..note:: `globals.init_session_collection()` must be called before + attempting to add to schema cache. + + Args: + config_path: Path to json schema config file. + + Raises: + RuntimeError + ValueError + """ + raw_config = _read_schema_config(config_path) + + # Cache type id and display name info for configured types + for schema in raw_config.get("schemas", {}): + type_name = schema.get("name", "") + type_version = schema.get("version", "") + display_name = schema.get("display_name", "") + if type_name and type_version: + type_id = _compose_schema_id(type_name, type_version) + _schema_ids[type_name] = type_id + if display_name: + _schema_display_names[type_id] = display_name + + # Add configured types to schema tree cache + for schema in raw_config.get("schemas", {}): + type_name = schema.get("name", "") + parent_types = schema.get("inherits", []) + # strip "$ref:" suffix + parent_types = [pt[5:] for pt in parent_types] + # convert to full ids + parent_types = [get_schema_id(pt) for pt in parent_types] + # always include base type + parent_types.append(BASE_TYPE_ID) + type_id = get_schema_id(type_name) + # store ancestral relationship + if type_id: + _schema_tree[type_id] = parent_types + + +def get_schema_id(type_name: str) -> str | None: + """Return full type id of type name if cached. + + Args: + type_name: Base name of schema type (e.g. "type.template"). + + Returns: + Full id of type, or None if type is not cached. + """ + return _schema_ids.get(type_name, None) + + +@trace +def get_schema_display_name(type_id: str) -> str | None: + """Return display name of schema of given its type id. + + Args: + type_id: Schema type id to be queried. + + Returns: + Display name of schema if set, or None if not set. + + Raises: + FlowError + """ + logger = get_logger(__name__) + + if type_id in _schema_display_names: + return _schema_display_names[type_id] + + # Type is not in schema config, must query display name + client = get_client() + q_input = flow_model.GetSchemaDisplayDataInput(schema_type_id=type_id) + q_schema_display = client.service_schema.schema_display_data(q_input) + try: + logger.info(f"Querying schema display data for type id: {type_id}.") + r_schema_display = q_schema_display.call() + except GQLAPIError as exc: + msg = f"Error querying schema display data for type id: {type_id}. {exc}" + raise FlowError(msg) from exc + display_data = r_schema_display.schema_display_data + if display_data.display_name == flow_model.NOT_SET: + return None + # Cache display name before returning + _schema_display_names[type_id] = display_data.display_name + return display_data.display_name + + +@cache +@trace +def is_sub_type(base_id: str, type_id: str) -> bool: + """Return True if provided type is a sub class of base type. + + Args: + base_id: String base type id. + type_id: String type id. + + Returns: + True if type_id derives from base_id. + + Raises: + FlowError + """ + logger = get_logger(__name__) + + if base_id == type_id: + return True + + # Check schema tree cache for input type + def match_ancestor(base_id, type_id): + if type_id not in _schema_tree: + raise ValueError("Unregistered type id.") + parent_types = _schema_tree[type_id] + for parent_type in parent_types: + if parent_type == base_id: + return True + if match_ancestor(base_id, parent_type): + return True + return False + + try: + return match_ancestor(base_id, type_id) + except ValueError: + # The type in question is not in our config cache + # We are forced to make a query + msg = f'Querying schema subclasses for base type "{base_id}" to find type "{type_id}".' + logger.info(msg) + client = get_client() + session_collection = get_session_collection() + q_input = flow_model.SchemasBySuperTypeInput( + collection_id=session_collection.id, + type_id=base_id, + include_sub_sub_classes=True, + ) + q_schema = client.service_schema.schemas_by_super_type(q_input) + try: + # NOTE: Iterator wraps q_schema.call() so no need to invoke this + # exlicitly. + for subtype in q_schema.schema_types_iterator: + if type_id == subtype: + return True + return False + except GQLAPIError as exc: + msg = f'Error querying subtypes of base type "{base_id}": {exc}' + raise FlowError(msg) from exc + + +def get_schema_config_version(config_path: str) -> str: + """Retrieve the schema config version from a config.json file. + + Args: + config_path: Path to the schema config json file. + + Returns: + The schema config version, or 'unknown' if the key is not present. + + Raises: + RuntimeError: If the config file is not found. + ValueError: If the config file contains invalid JSON. + """ + config = _read_schema_config(config_path) + return config.get("version", "unknown") diff --git a/python/tk-core/python/tank_vendor/flow_integration_sdk/schema_builder.py b/python/tk-core/python/tank_vendor/flow_integration_sdk/schema_builder.py new file mode 100644 index 00000000..d468af81 --- /dev/null +++ b/python/tk-core/python/tank_vendor/flow_integration_sdk/schema_builder.py @@ -0,0 +1,614 @@ +# Copyright (c) 2026 Shotgun Software Inc. +# +# CONFIDENTIAL AND PROPRIETARY +# +# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit +# Source Code License included in this distribution package. See LICENSE. +# By accessing, using, copying or modifying this work you indicate your +# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights +# not expressly granted therein are reserved by Shotgun Software Inc. + +"""Schema builder utilities for creating and managing Flow pipeline schemas.""" + +from __future__ import annotations # needed for python 3.9 support + +from typing import Any + +from tank_vendor.flow_data_sdk.base import model as flow_model +from tank_vendor.flow_data_sdk.base.exceptions import ( + FlowConnectionError, + GQLAPIError, + ValidationError, +) + +from .exceptions import ( + FlowSchemaBuilderError, + FlowSchemaDisplayDataError, + FlowSchemaError, + FlowSchemaLibraryError, +) +from .globals import BASE_TYPE_ID, get_client +from .objects import FlowProject +from . import schema +from .schema import get_schema_id +from .utils import cleanpath, get_logger + + +# Constant for Flow Toolkit Library schema library ID +FLOW_TOOLKIT_LIBRARY_ID = "FlowToolkitLibrary" + + +class SchemaBuilder: + """Utility class to build and validate schemas from a dictionary. + + This class provides methods to validate schema definitions, build new schemas, + update schema display names, and retrieve existing schemas from a schema library. + """ + + def __init__( + self, + schema_dict: dict, + schema_kind: flow_model.SchemaKind, + project_id: str, + schema_library_id: str, + ): + """Initialize the SchemaBuilder. + + Args: + schema_dict: The dictionary containing the schema definition. + schema_kind: The kind of Schema to create (type, component, or property). + project_id: The Flow AM project ID. + schema_library_id: The ID of the schema library where the schema + will be created. + """ + self.schema_dict = schema_dict + self.schema_kind = schema_kind + self.project_id = project_id + self.schema_library_id = schema_library_id + self.schema: flow_model.Schema | None = None + + # Validate schema dictionary on initialization + self.__validate_schema_dict() + + def __validate_schema_dict(self) -> None: + """Validate the incoming schema dictionary. + + Raises: + KeyError: If required keys are missing or invalid. + ValueError: If key types or values are invalid. + """ + + # Define a dictionary mapping keys to their expected types + key_type_dict = { + "name": str, + "version": str, + "display_name": str, + "kind": str, + "description": str, + "inherits": list, + "properties": list, + } + + # Check for mandatory keys + for key in ["name", "version"]: + if key not in self.schema_dict: + raise KeyError( + f"The custom type schema must contain a '{key}' key of type string." + ) + + # Validate keys and types + for key, value in self.schema_dict.items(): + if key not in key_type_dict: + raise KeyError(f"Invalid key '{key}' in custom type schema.") + if not isinstance(value, key_type_dict[key]): + raise ValueError( + f"The '{key}' key must be of type {key_type_dict[key].__name__}." + ) + if key == "kind": + # Validate against flow_model.SchemaKind enum + valid_kinds = [kind.value for kind in flow_model.SchemaKind] + if value not in valid_kinds: + raise ValueError( + f"Invalid kind '{value}' in custom type schema. " + f"Must be one of {', '.join(valid_kinds)}" + ) + if key == "properties": + for property_dict in value: + self.__validate_property_dict(property_dict) + + def __validate_property_dict(self, property_dict: dict) -> None: + """Validate a schema 'property' dictionary. + + Args: + property_dict (dict): The property dictionary to validate. + + Raises: + KeyError: If required keys are missing or invalid. + ValueError: If key types or values are invalid. + """ + + # Define a dictionary mapping keys to their expected types + key_type_dict = { + "name": str, + "data_type": str, + "properties": list, + "default_value": Any, + "context": str, + "length": int, + } + + # Check for mandatory keys + for key in ["name", "data_type"]: + if key not in property_dict: + raise KeyError(f"The property schema must contain a '{key}' key.") + + # Validate keys and types + for key, value in property_dict.items(): + if key not in key_type_dict: + raise KeyError(f"Invalid key '{key}' in property schema.") + + if key_type_dict[key] is not Any: + if not isinstance(value, key_type_dict[key]): # type: ignore + raise ValueError( + f"The '{key}' key must be of type " + f"{key_type_dict[key].__name__}." # type: ignore + ) + + if key == "data_type": + # Validate that referenced schema exists in config + if "$ref:" in value: + referenced_schema = value.split(":")[1] + referenced_schema_id = get_schema_id(referenced_schema) + if not referenced_schema_id: + raise ValueError( + f"Referenced schema '{referenced_schema}' not found " + "in config.json." + ) + + if key == "properties": + for sub_property_dict in value: + self.__validate_property_dict(sub_property_dict) + + def _create_property_type_input( + self, data_type: str + ) -> flow_model.PropertyTypeInput: + """Create a PropertyTypeInput from a data type string. + + Args: + data_type: Either a primitive type name (e.g., "String") or a + custom schema type ID + + Returns: + PropertyTypeInput with either primitive_type or custom_type set + """ + primitive_types = [member.value for member in flow_model.PrimitiveType] + + if data_type in primitive_types: + primitive_enum = flow_model.PrimitiveType(data_type) + return flow_model.PropertyTypeInput(primitive_type=primitive_enum.value) + + return flow_model.PropertyTypeInput(custom_type=data_type) + + def _create_properties_definition_input( + self, property_dict: dict + ) -> flow_model.PropertiesDefinitionInput: + """Recursively create a PropertiesDefinitionInput from a property dictionary.""" + # Resolve references in data type + data_type = property_dict["data_type"] + + if "$ref:" in data_type: + # Resolve referenced schema id + referenced_schema_name = property_dict["data_type"].split(":")[1] + referenced_schema_id = get_schema_id(referenced_schema_name) + if not referenced_schema_id: + raise ValueError( + f"Referenced schema '{referenced_schema_name}' not found " + "in config.json." + ) + data_type = referenced_schema_id + + if "$id:" in data_type: + data_type = ":".join(data_type.split(":")[1:]) + + # Create PropertyTypeInput + property_type_input = self._create_property_type_input(data_type) + + # Recursively handle nested properties + nested_properties = [] + if property_dict.get("properties"): + for nested_property in property_dict["properties"]: + nested_properties.append( + self._create_properties_definition_input(nested_property) + ) + + # Create PropertiesDefinitionInput + properties_definition_input = flow_model.PropertiesDefinitionInput( + name=property_dict["name"], + type=property_type_input, + properties=nested_properties if nested_properties else None, + ) + + # Add optional fields if present + if "default_value" in property_dict: + properties_definition_input.value = property_dict["default_value"] + if "context" in property_dict: + properties_definition_input.context = property_dict["context"] + if "length" in property_dict: + properties_definition_input.length = property_dict["length"] + + return properties_definition_input + + def build(self) -> flow_model.Schema: + """Build and commit the custom private Schema. + + Returns: + Schema: The created or existing Schema instance. + + Raises: + FlowSchemaError: If schema creation or commit fails due to a client error. + ValueError: If referenced schemas in 'inherits' or 'properties' are + not found. + """ + + logger = get_logger(__name__) + client = get_client() + + # Build inherits list for CreateSchemaInput + inherits = [] + if self.schema_dict.get("inherits"): + for inherited_schema in self.schema_dict["inherits"]: + # If current schema inherits from another schema + if "$ref:" in inherited_schema: + # Resolve referenced schema id + inherited_schema_name = inherited_schema.split(":")[1] + inherited_schema_id = get_schema_id(inherited_schema_name) + if not inherited_schema_id: + raise ValueError( + f"Inherited schema '{inherited_schema_name}' not found " + "in config.json." + ) + inherits.append(inherited_schema_id) + else: + inherits.append(inherited_schema) + + # Build property list for CreateSchemaInput + properties = [] + for property_dict in self.schema_dict.get("properties", []): + properties.append( + self._create_properties_definition_input(property_dict) + ) + + try: + # Create CreateSchemaInput for the mutation + create_schema_input = flow_model.CreateSchemaInput( + kind=self.schema_kind.value, + name=self.schema_dict["name"], + project_id=self.project_id, + schema_library_id=self.schema_library_id, + version=self.schema_dict["version"], + description=self.schema_dict.get("description", ""), + inherits=inherits if inherits else None, + properties=properties if properties else None, + ) + # Create and call the schema mutation + create_schema_query = client.service_schema.create_schema( + variables=create_schema_input + ) + query_response = create_schema_query.call() + self.schema = query_response.schema + logger.info("Created new schema: %s", self.schema.name) + except (GQLAPIError, FlowConnectionError, ValidationError) as e: + raise FlowSchemaError( + details=( + f"Failed to create schema '{self.schema_dict['name']}' " + f"version '{self.schema_dict['version']}': {e}" + ) + ) from e + + return self.schema + + def get_display_data(self) -> flow_model.SchemaDisplayData | None: + """Retrieve the display data object for the current schema. + + This method attempts to fetch the SchemaDisplayData associated with the + schema instance. If the schema has not been built, a FlowSchemaBuilderError + is raised. If no display data exists for the schema, None is returned. + + Returns: + SchemaDisplayData | None: The display data instance if found, + otherwise None. + + Raises: + FlowSchemaBuilderError: If the schema instance has not been built. + """ + + if not self.schema: + raise FlowSchemaBuilderError(details="Schema instance has not been built yet.") + + client = get_client() + schema_display_data = None + try: + # Create GetSchemaDisplayDataInput for the query + input_data = flow_model.GetSchemaDisplayDataInput( + schema_type_id=self.schema.type_id + ) + # Create and call the schema query + schema_query = client.service_schema.schema_display_data( + variables=input_data + ) + query_response = schema_query.call() + schema_display_data = query_response.schema_display_data + except (GQLAPIError, FlowConnectionError, ValidationError): + # TODO: Verify get display data for new created schema's behavior + # Two possible scenarios: + # 1. Schema type doesn't exist (shouldn't happen if schema was just created) + # 2. No display data exists yet (expected for newly created schemas) + pass + + return schema_display_data + + def update_display_name(self) -> str | None: + """Update the display name of the schema to match the value in the schema + definition. + + This method retrieves the current schema's display data and updates its + display name if it differs from the desired value. If the display name is + successfully updated, the new value is returned. If the display name + already matches, it is returned. + + Returns: + str | None: The updated display name if successful, or the current + display name if it already matches the desired value. + + Raises: + FlowSchemaBuilderError: If the schema instance has not been built or the + schema definition does not specify a display name. + FlowSchemaDisplayDataError: If updating the display name fails due to an + error (such as an API failure). + """ + + logger = get_logger(__name__) + client = get_client() + display_name = self.schema_dict.get("display_name") + + if not self.schema: + raise FlowSchemaBuilderError(details="Schema instance has not been built yet.") + + if not display_name: + raise FlowSchemaBuilderError( + details="No display name specified in schema definition." + ) + + if ( + self.schema.display_data + and self.schema.display_data.display_name == display_name + ): + return display_name + + schema_display_data = self.get_display_data() + + try: + if schema_display_data: + # Display data exists + # Create UpdateSchemaDisplayDataInput for update display name + # mutation and call it + input_data = flow_model.UpdateSchemaDisplayDataInput( + schema_type_id=self.schema.type_id, display_name=display_name + ) + client.service_schema.update_schema_display_data( + variables=input_data, + ).call() + else: + # Display data doesn't exist + # Create CreateSchemaDisplayDataInput for create display name + # mutation and call it + input_data = flow_model.CreateSchemaDisplayDataInput( + schema_type_id=self.schema.type_id, display_name=display_name + ) + client.service_schema.create_schema_display_data( + variables=input_data, + ).call() + + logger.debug( + "updated display name of schema %s to '%s'", + self.schema.name, + display_name, + ) + return display_name + except (GQLAPIError, FlowConnectionError, ValidationError) as e: + raise FlowSchemaDisplayDataError( + details=( + f"Could not update display data for schema " + f"'{self.schema.name}': {e}." + ) + ) from e + + +def _get_schema_library( + library_id: str, collection_id: str +) -> flow_model.SchemaLibrary | None: + """Retrieve a schema library by its unique name (ID) if it exists, or None + if not found. + + Raises: + FlowSchemaLibraryError: If retrieving schema libraries fails due to a client + error. + """ + + logger = get_logger(__name__) + client = get_client() + + try: + # Create SchemaLibrariesByCollectionIdInput for the query + input_data = flow_model.SchemaLibrariesByCollectionIdInput( + collection_id=collection_id + ) + # Create and call the schema query + schema_query = client.service_schema.schema_libraries_by_collection_id( + variables=input_data + ) + schema_libraries = list(schema_query.libraries_iterator or []) + except (GQLAPIError, FlowConnectionError, ValidationError) as e: + raise FlowSchemaLibraryError( + details=f"Failed to retrieve all schema libraries: {e}" + ) from e + + for schema_library in schema_libraries: + if schema_library.name == library_id: + logger.info("Found existing schema library %s", library_id) + return schema_library + + logger.info("Schema Library %s not found.", library_id) + return None + + +def _create_schema_library( + library_id: str, + title: str, + description: str, + project_id: str, + collection_id: str, +) -> flow_model.SchemaLibrary: + """Create a schema library if it does not exist, or return the existing one. + + Raises: + FlowSchemaLibraryError: If retrieving or creating the schema library fails + due to a client error. + """ + + logger = get_logger(__name__) + schema_library = _get_schema_library(library_id, collection_id) + + if schema_library is not None: + logger.info("Schema Library %s found. Skipping creation.", library_id) + return schema_library + + client = get_client() + + try: + # Create CreateSchemaLibraryInput for the mutation + input_data = flow_model.CreateSchemaLibraryInput( + name=library_id, + title=title, + description=description, + project_id=project_id, + ) + # Create and call the schema mutation + schema_query = client.service_schema.create_schema_library( + variables=input_data + ) + query_response = schema_query.call() + # Extract the created schema library from the response + schema_library = query_response.schema_library + logger.info("Successfully created a new Schema Library %s", library_id) + return schema_library + + except (GQLAPIError, FlowConnectionError, ValidationError) as e: + raise FlowSchemaLibraryError( + details=f"Failed to create SchemaLibrary '{library_id}': {e}" + ) from e + + +def create_pipeline_schemas(project_id: str, config_path: str): + """Create pipeline schemas under the schema library 'Flow Toolkit Library' + for a CPA collection. + + This utility is only applicable to CPA (provisioned) collections. It is + designed to create or use the 'Flow Toolkit Library' and its associated + schemas. + + The function performs the following steps: + 1. Loads and validates the config.json file. + 2. Queries existing schema type ids for the target collection. + 3. Creates the schema library if any configured schemas are missing. + 4. Creates any missing schemas and sets their display names. + + Args: + project_id: The Flow AM project ID. + config_path: Path to the schema config json file. + Raises: + RuntimeError: If schema library creation fails. + FileNotFoundError: If the config file does not exist. + json.JSONDecodeError: If the config file contains invalid JSON. + KeyError: If the config file does not contain a 'schemas' key, + or if required keys are missing in the schema definition. + ValueError: If schema definition or property values are invalid. + FlowSchemaError: If schema creation or retrieval fails. + FlowSchemaBuilderError: If schema building fails. + FlowSchemaDisplayDataError: If updating display data fails. + FlowSchemaLibraryError: If schema library operations fail. + """ + logger = get_logger(__name__) + + config = schema._read_schema_config(config_path) + if "schemas" not in config: + raise KeyError("The schema config file must contain a 'schemas' key with a list of schemas to create.") + client = get_client() + collection_id = FlowProject.get_collection_id(project_id) + + # Retrieve all existing published schema type ids for the collection + try: + # Create SchemasBySuperTypeInput for the query + schemas_by_supertype_input = flow_model.SchemasBySuperTypeInput( + collection_id=collection_id, + type_id=BASE_TYPE_ID, + include_sub_sub_classes=True, + ) + # Create and call the schema query + schema_query = client.service_schema.schemas_by_super_type( + variables=schemas_by_supertype_input + ) + existing_schema_types = set(schema_query.schema_types_iterator) + logger.info( + f"Retrieved {len(existing_schema_types)} existing schemas for " + f"collection {collection_id}." + ) + except (GQLAPIError, FlowConnectionError, ValidationError) as e: + raise RuntimeError(f"Failed to retrieve existing schemas: {e}") from e + + # Check if schema listed in config.json already exists + # If not, add it to need_to_create list + need_to_create = [] + for schema_dict in config.get("schemas", []): + # Get the schema type id for the schema and check if it already exists + schema_type_id = get_schema_id(schema_dict["name"]) + if schema_type_id not in existing_schema_types: + logger.info( + "Schema '%s' not found. Adding to list to be created.", + schema_type_id, + ) + need_to_create.append(schema_dict) + + # Check if need_to_create is not empty + # and proceed to create schema library and schemas + if len(need_to_create) > 0: + logger.info("Attempting to create missing schemas...") + library_id = FLOW_TOOLKIT_LIBRARY_ID + _create_schema_library( + library_id=library_id, + title="Flow Toolkit Library", + description="Schemas for Flow Toolkit pipelines.", + project_id=project_id, + collection_id=collection_id, + ) + + schemas = [] + # Create schemas if not exist + for schema_dict in need_to_create: + schema_builder = SchemaBuilder( + schema_dict=schema_dict, + schema_kind=flow_model.SchemaKind(schema_dict["kind"]), + project_id=project_id, + schema_library_id=library_id, + ) + schema_builder.build() + + if schema_builder.schema: + # NOTE: We need to explicitly pass the display name back because + # in newly created schemas, the 'display_name' property is not + # updated, therefore can not be reliably used to determine + # display name of the schema. + display_name = schema_builder.update_display_name() + schemas.append((schema_builder.schema, display_name)) + else: + logger.info("All required schemas already exist.") diff --git a/python/tk-core/python/tank_vendor/flow_integration_sdk/storage.py b/python/tk-core/python/tank_vendor/flow_integration_sdk/storage.py new file mode 100644 index 00000000..d2c927a1 --- /dev/null +++ b/python/tk-core/python/tank_vendor/flow_integration_sdk/storage.py @@ -0,0 +1,314 @@ +# - +# ***************************************************************************** +# Copyright 2026 Autodesk, Inc. All rights reserved. +# +# These coded instructions, statements, and computer programs contain +# unpublished proprietary information written by Autodesk, Inc. and are +# protected by Federal copyright law. They may not be disclosed to third +# parties or copied or duplicated in any form, in whole or in part, without +# the prior written consent of Autodesk, Inc. +# ***************************************************************************** +# + + +""" +This module provides utilities related to storage locations. +""" + +from __future__ import annotations # needed for python 3.9 support + +import os +import json +from functools import cache + +from tank_vendor.flow_data_sdk.base import model as medm_model + +from .exceptions import ( + ConfigurationError, + DirectoryNotCreatedError, + FlowError, +) +from .utils import ( + abspath, + cleanpath, + ensure_dir, + get_logger, + trace, +) + + +# Storage roots +# ------------- +# Environment variables that store key storage root paths. +# Paths should be configured via set_sandbox_root() and set_storage_root(). +# They should be accessed via get_sandbox_root() and get_storage_root() + +# Sandbox storage root - for draft assets/revisions (editable) +FLOW_SANDBOX_ROOT = "FLOW_SANDBOX_ROOT" + +# Primary cache root - for binaries cached locally from remote storage (read-only) +FLOW_STORAGE_ROOT = "FLOW_STORAGE_ROOT" + + +def set_sandbox_root(sandbox_root: str, create_dir: bool = False): + """Set sandbox root location. + + Args: + sandbox_root: Path to top-level sandbox directory. + create_dir: If True, create this directory if it doesn't exist. + + Raises: + DirectoryNotCreatedError + """ + logger = get_logger(__name__) + + # Expand user paths (e.g., ~ to /Users/username) + sandbox_root = os.path.expanduser(sandbox_root) + # Ensure path is absolute and real + sandbox_root = abspath("", sandbox_root) + logger.info(f"Setting Flow sandbox directory to: {sandbox_root}") + if create_dir: + try: + # Create directory if necessary + ensure_dir(sandbox_root) + except DirectoryNotCreatedError as exc: + raise ConfigurationError( + details=f"Couldn't create directory {sandbox_root} for sandbox root." + ) from exc + + os.environ[FLOW_SANDBOX_ROOT] = sandbox_root + + +def set_storage_root(storage_root: str, create_dir: bool = False): + """Set primary cache location. + + Args: + storage_root: Path to top-level primary cache directory. + create_dir: If True, create this directory if it doesn't exist. + + Raises: + DirectoryNotCreatedError + """ + logger = get_logger(__name__) + + # Expand user paths (e.g., ~ to /Users/username) + storage_root = os.path.expanduser(storage_root) + # Ensure path is absolute and real + storage_root = abspath("", storage_root) + logger.info(f"Setting Flow storage directory to: {storage_root}") + if create_dir: + try: + # Create directory if necessary + ensure_dir(storage_root) + except DirectoryNotCreatedError as exc: + raise ConfigurationError( + details=f"Couldn't create directory {storage_root} for primary storage root." + ) from exc + os.environ[FLOW_STORAGE_ROOT] = storage_root + + +def get_sandbox_root() -> str: + """Return the root directory of sandbox location. + + Returns: + Full path to configured sandbox root directory. + """ + return os.getenv(FLOW_SANDBOX_ROOT, "") + + +def get_storage_root() -> str: + """Return the root directory of primary cache location. + + Returns: + Full path to configured storage root directory. + """ + return os.getenv(FLOW_STORAGE_ROOT, "") + + +def get_storage_key(asset_id: str) -> str: + """Parse storage key from asset id. + This is a unique file-safe id that can be used in storage file paths. + + Args: + asset_id: Medm asset id. + + Returns: + Storage key associated with that asset. + """ + return asset_id.rsplit(":", maxsplit=1)[-1] + + +def get_storage_asset_dir(asset_id: str) -> str: + """Return the full path of asset directory in primary storage + (whether or not the directory exists). + + Args: + asset_id: Medm asset id. + + Returns: + Full path to expected location of primary storage directory on local disk. + """ + return cleanpath(get_storage_root(), get_storage_key(asset_id)) + + +def get_storage_revision_dir(asset_id: str, revision_number: int) -> str: + """Return the full path of asset directory in primary storage + for the given revision of asset (whether or not the directory exists). + + Args: + asset_id: Medm asset id. + revision_number: Number of revision. + + Returns: + Full path to expected location of primary storage directory on local disk. + """ + return cleanpath(get_storage_asset_dir(asset_id), f"r{revision_number}") + + +@trace +def _cache_asset_info(asset_id: str): + """Store relevant, persistent metadata about this asset to its + storage directory. This will be very useful for certain lookups. + + In particular, we want to cache the relationship between the asset's + storage key and asset id. We do this by saving a sidecar file to the + asset's storage directory. Looking this up via the sidecar file will + save a very expensive query when mapping storage key back to asset id. + + Args: + asset_id: Id of asset whose info should be cached. + + Raises: + FlowError + """ + # Check if the info file already exists + # If so, check that it is complete. File reads are cheap so doing + # this every time an Asset object is constructed should be ok. + storage_dir = get_storage_asset_dir(asset_id) + file_path = cleanpath(storage_dir, ".info") + info = {} + if os.path.exists(file_path): + with open(file_path, "r") as f: + try: + info = json.loads(f.read()) + except json.JSONDecodeError: + # We don't care if the file is corrupted, just write a new one + pass + + # For now we're only storing asset id + expected_keys = ["asset_id"] + write_file = False + for key in expected_keys: + if key not in info: + # If the file is incomplete, re-write the file + write_file = True + break + + if not write_file: + return + + info = {"asset_id": asset_id} + try: + if not os.path.exists(storage_dir): + os.makedirs(storage_dir) + with open(file_path, "w") as f: + f.write(json.dumps(info, indent=4)) + except Exception as exc: # pylint: disable=broad-except + msg = f"Asset info file could not be written: {file_path}" + raise FlowError(msg) from exc + + +@cache +@trace +def storage_key_to_asset_id(storage_key: str) -> str: + """Given an asset storage key, return the corresponding asset id. + + Args: + storage_key: String asset storage key. + + Returns: + Asset id. + + Raises: + FlowError + """ + # Check if storage key to asset id lookup is cached + info_file = cleanpath(get_storage_root(), storage_key, ".info") + if os.path.exists(info_file): + with open(info_file, "r") as f: + info = json.loads(f.read()) + asset_id = info.get("asset_id") + if asset_id is not None: + return asset_id + + # Fallback plan is to do a very expensive project query + # TODO: implement query + + raise FlowError(f"Invalid storage key provided: {storage_key}") + + +def get_storage_component_path( + revision: medm_model.AssetRevision, + component_name: str = "", + component_purpose: str = "", + blob_index: int = 0, +) -> str | None: + """Return the full path of the component blob file of the given asset revision + in primary storage (whether or not the file exists). + + Args: + revision: Revision to which component belongs. + component_name: If provided, search for component with this name and return its storage path. + This should be unique within the revision. + component_purpose: If provided, search for a component with this purpose and return + its storage path. There may be multiple components with the same purpose, + so the first match will be returned. + blob_index: Specific blob from source component to get. + + ..note:: If both component name and purpose are provided, the first intersection + of both criteria will be returned. + + Returns: + Full path to expected location of cached source file on local disk, or + None if the component does not exist on the revision. + + Raise: + FlowError + """ + comp = _find_component(revision, name=component_name, purpose=component_purpose) + if not comp: + return None + try: + blob_path = comp.data["data"][blob_index]["path"] + except KeyError as exc: + msg = f'Blob path could not be retrieved for component "{comp.name}". {exc}' + raise FlowError(msg) + storage_dir = get_storage_revision_dir(revision.asset_id, revision.revision_number) + return cleanpath(storage_dir, blob_path) + + +def _find_component( + revision: medm_model.AssetRevision, + name: str = "", + purpose: str = "", + type_id: str = "", +) -> medm_model.Component | None: + """Match component on given revision based on criteria. + If multiple criteria is provided, the first intersection will be returned. + + Args: + revision: Revision to be searched. + name: Name of component to match. + purpose: Purpose of component to match. + NOTE: this is only applicable to binary components. + type_id: Type id of component to match. + """ + for comp in revision.components: + comp_purpose = comp.data.get("purpose", "") + if name and comp.name != name: + continue + if purpose and comp_purpose != purpose: + continue + if type_id and comp.type_id != type_id: + continue + return comp + return None diff --git a/python/tk-core/python/tank_vendor/flow_integration_sdk/transferapi.py b/python/tk-core/python/tank_vendor/flow_integration_sdk/transferapi.py new file mode 100644 index 00000000..0e1d6caf --- /dev/null +++ b/python/tk-core/python/tank_vendor/flow_integration_sdk/transferapi.py @@ -0,0 +1,251 @@ +# - +# ***************************************************************************** +# Copyright 2026 Autodesk, Inc. All rights reserved. +# +# These coded instructions, statements, and computer programs contain +# unpublished proprietary information written by Autodesk, Inc. and are +# protected by Federal copyright law. They may not be disclosed to third +# parties or copied or duplicated in any form, in whole or in part, without +# the prior written consent of Autodesk, Inc. +# ***************************************************************************** +# + + +""" +API methods for transfering files to the remote server +""" + +from __future__ import annotations # needed for python 3.9 support + +import base64 +import hashlib +import math +import os +import urllib.error +import urllib.request + +from tank_vendor.flow_data_sdk.base import model as medm_model +from tank_vendor.flow_data_sdk.base.exceptions import GQLAPIError + +from .exceptions import FileUploadError +from .utils import get_logger, trace + + +@trace +def open_upload_file(client, urn_id: str, upload_uri: str) -> medm_model.UploadFileJob: + """Open an asynchronous file upload session. + + Creates an upload job on the server for multipart file upload. + The returned job ID is required for subsequent upload operations. + + Args: + urn_id: Target blob URN identifier. + upload_uri: Server-provided upload URI. + + Returns: + An UploadFileJob instance containing the job ID + + Raises: + FileUploadError: If the GraphQL API returns an error. + """ + logger = get_logger(__name__) + + try: + # Create the open upload file input data + input_data = medm_model.OpenUploadFileInput( + upload_uri=upload_uri, urn_id=urn_id + ) + # Create the open upload file operation and call it + operation = client.service_binary.open_upload_file(variables=input_data) + response = operation.call() + return response.job + + except GQLAPIError as e: + error_details = f"GraphQL API error (status {e.status_code}): {e.message}" + logger.error(f"Failed to open upload file session: {error_details}") + raise FileUploadError(file_path=upload_uri, details=error_details) from e + + +@trace +def get_upload_file_part( + client, async_job_id: str, part_num: int, md5_hash: str +) -> medm_model.GetUploadFilePartResponse: + """Get upload URL for a specific file part + + Retrieves upload information for a specific part of the multipart upload for the single file upload. + + Args: + async_job_id: The async job ID returned from open_upload_file. + part_num: The part number to upload (1-based index). + md5_hash: Base64-encoded MD5 hash of the part data. + + Returns: + UploadFilePart with upload URL and metadata. + + Raises: + FileUploadError: If the GraphQL API returns an error. + """ + logger = get_logger(__name__) + + try: + # Create the get upload file part input data + input_data = medm_model.GetUploadFilePartInput( + async_job_id=async_job_id, hash=md5_hash, part_num=part_num + ) + # Create the get upload file part operation and call it + operation = client.service_binary.get_upload_file_part(variables=input_data) + response = operation.call() + return response + + except GQLAPIError as e: + error_details = f"GraphQL API error (status {e.status_code}): {e.message}" + logger.error( + f"Failed to get upload file part {part_num} for job {async_job_id}: {error_details}" + ) + raise FileUploadError( + file_path=f"job_{async_job_id}_part_{part_num}", details=error_details + ) from e + + +@trace +def close_upload_file( + client, async_job_id: str, state: str, etags: list[str] +) -> medm_model.UploadFileJob: + """Close the asynchronous file upload session. + + Notifies the server to merge all uploaded parts into the final file. + Must be called even if the upload failed to clean up server resources. + + Args: + async_job_id: The async job ID returned from open_upload_file. + state: Final state of the upload. Must be "SUCCEEDED" or "FAILED". + etags: List of ETags for each uploaded part. + + Returns: + An UploadFileJob instance containing the job ID + + Raises: + ValueError: If an invalid state is provided. + FileUploadError: If the GraphQL API returns an error. + """ + logger = get_logger(__name__) + + if state not in ["SUCCEEDED", "FAILED"]: + raise ValueError(f"Invalid state: {state}. Must be SUCCEEDED or FAILED") + + try: + # Create the close upload file input data + input_data = medm_model.CloseUploadFileInput( + async_job_id=async_job_id, + state=state, + etags=etags, + ) + # Create the close upload file operation and call it + operation = client.service_binary.close_upload_file(variables=input_data) + response = operation.call() + return response.job + + except GQLAPIError as e: + error_details = f"GraphQL API error (status {e.status_code}): {e.message}" + logger.error( + f"Failed to close upload file session {async_job_id} with state {state}: {error_details}" + ) + raise FileUploadError( + file_path=f"job_{async_job_id}", details=error_details + ) from e + + +@trace +def upload_blob(client, file_path, urn_id, upload_uri): + """Upload a file to remote storage using multipart upload. + + Upload workflow: + 1. Opens an upload session and obtains a job ID + 2. Splits the file into chunks and uploads each part + 3. Closes the upload session to finalize + + Args: + client: The GraphQL client instance + file_path: The local filename of the file to be uploaded + urn_id: URN of the blob component to upload to + upload_uri: URI needed for API transfer + """ + logger = get_logger(__name__) + + # Create the UploadFileJob + upload_job = open_upload_file(client=client, urn_id=urn_id, upload_uri=upload_uri) + async_job_id = upload_job.id + logger.info(f"Opened upload job: {async_job_id} (state: {upload_job.state})") + + # Keep track of etags + etags = [] + + # Split file into chunks for upload + total_size = os.path.getsize(file_path) + part_size = max(5 * 1024 * 1024, math.ceil(total_size / 10000)) + total_parts = math.ceil(total_size / part_size) + + logger.info(f"Uploading {file_path} in {total_parts} chunks of {part_size} bytes") + + upload_state = "SUCCEEDED" + + try: + with open(file_path, "rb") as f: + for part_num in range(1, total_parts + 1): + # Read chunk data + chunk_data = f.read(part_size) + # MD5 Hash is only used as a checksum so there is + # no security risk for using it here + md5_hash = base64.b64encode(hashlib.md5(chunk_data).digest()).decode( + "utf-8" + ) + + logger.info(f" - Uploading chunk {part_num} : {md5_hash}") + + # Get the upload URL for this part + part_info = get_upload_file_part( + client=client, + async_job_id=async_job_id, + part_num=part_num, + md5_hash=md5_hash, + ) + upload_url = part_info.send_url + # Confirm that upload url is a web url and not a file:// url + # which could present a security breach + if not upload_url.startswith("http"): + msg = f"Suspicious url returned: {upload_url}. Aborting upload..." + raise RuntimeError(msg) + + # Create request and upload the part data + headers = { + "Content-MD5": md5_hash, + "Content-Type": "application/octet-stream", + } + req = urllib.request.Request( + upload_url, data=chunk_data, headers=headers, method="PUT" + ) + with urllib.request.urlopen(req, timeout=120) as response: + if response.status not in (200, 201): + raise ValueError( + f"Failed to upload part {part_num}: HTTP {response.status}" + ) + etag = response.headers.get("ETag") + if not etag: + raise ValueError( + f"ETag not found in response for part {part_num}" + ) + etags.append(etag) + logger.info(f" Part {part_num}/{total_parts} uploaded successfully") + + except Exception as e: + upload_state = "FAILED" + + raise FileUploadError(file_path=file_path, details=str(e)) from e + + finally: + # Finally close the file upload job + close_upload_file( + client=client, + async_job_id=async_job_id, + etags=etags, + state=upload_state, + ) diff --git a/python/tk-core/python/tank_vendor/flow_integration_sdk/utils.py b/python/tk-core/python/tank_vendor/flow_integration_sdk/utils.py new file mode 100644 index 00000000..8301f2a9 --- /dev/null +++ b/python/tk-core/python/tank_vendor/flow_integration_sdk/utils.py @@ -0,0 +1,386 @@ +# - +# ***************************************************************************** +# Copyright 2026 Autodesk, Inc. All rights reserved. +# +# These coded instructions, statements, and computer programs contain +# unpublished proprietary information written by Autodesk, Inc. and are +# protected by Federal copyright law. They may not be disclosed to third +# parties or copied or duplicated in any form, in whole or in part, without +# the prior written consent of Autodesk, Inc. +# ***************************************************************************** +# + + +""" +This module contains general utilities that can be leveraged by Flow integrations. +""" + +from __future__ import annotations # needed for python 3.9 support + +import logging +import math +import mimetypes +import os +import re +import sys +import time +import traceback +import urllib +from functools import wraps +from typing import Callable + +from .exceptions import DirectoryNotCreatedError, FlowError + + +# Time in seconds above which profiling info should be printed +# Setting the value to None will turn off profiling globally +# Add/Remove the @trace decorator from a function to turn on/off +# profiling for that function +PROFILING_THRESHOLD = None + +# Logging level - used for default logger only +LOGGING_LEVEL = "INFO" +# Callback function used to generate logger object +# (If unset, default python logger will be used) +_logger_callback: Callable[[str], logging.Logger] | None = None +# Logging formatter for default python logger +_formatter = logging.Formatter( + "%(asctime)s - %(module)s.%(funcName)s [%(lineno)d] %(levelname)s: %(message)s" +) + + +def trace(func): + """Decorator to track and record execution time of a function.""" + + def profile_output(arg0, t0, t1): + """Output profiling info to log.""" + # List of internal object types we would want to trace + supported_objects = [ + "FlowAsset", + "FlowRevision", + "FlowBlob", + "FlowComponent", + "ComponentSpec", + "FlowProject", + ] + msg = "PROFILING: " + if arg0.__class__ == type: + # Class function + msg += f"{arg0.__name__}.{func.__name__}" + elif arg0.__class__.__name__ in supported_objects: + # Member function + msg += f"{arg0.__class__.__name__}.{func.__name__}" + else: + # Global function + msg += func.__name__ + msg += f": {strtime(t0, t1)}" + # Only display execution times above threshold + if t1 - t0 >= PROFILING_THRESHOLD: + print(msg) + + def strtime(t0, t1): + """Convert a time range into MM:SS.remainder string format.""" + td = t1 - t0 + minutes = math.floor(td / 60) + seconds = td - (minutes * 60) + return f"{minutes:02}:{seconds:02}" + + @wraps(func) + def wrapper(*args, **kwargs): + # If profiling is turned off, run function normally + if PROFILING_THRESHOLD is None: + return func(*args, **kwargs) + + t0 = time.time() + try: + result = func(*args, **kwargs) + except Exception as e: + raise e + finally: + t1 = time.time() + arg0 = args[0] if args else None + profile_output(arg0, t0, t1) + return result + + return wrapper + + +def get_logger(name: str): + """Return a logger with the given name. + The callback set in _logger_callback will be used to generate the logger. + """ + if _logger_callback is None: + # Generate default python logger + logger = logging.getLogger(name) + if len(logger.handlers) == 0: + # Initialize stdout handler + handler = logging.StreamHandler(sys.stdout) + handler.setFormatter(_formatter) + logger.addHandler(handler) + logger.setLevel(LOGGING_LEVEL) + return logger + return _logger_callback(name) + + +def to_regex_safe_wildcard_string(pattern: str) -> str: + """Convert a wildcard pattern to a regex-safe pattern string. + + This function escapes all regex special characters in the input pattern + and converts wildcard asterisks (*) to regex wildcards (.*), allowing + for safe pattern matching where only * acts as a wildcard. + + Args: + pattern: Input pattern that may contain wildcards (*) and special + regex characters that should be treated as literals. + + Returns: + A regex pattern string where: + - All regex special characters are escaped (treated as literals) + - Asterisks (*) are converted to .* (regex wildcard) + + Examples: + >>> to_regex_safe_wildcard_string("file.txt") + 'file\\\\.txt' + >>> to_regex_safe_wildcard_string("*.txt") + '.*\\\\.txt' + >>> to_regex_safe_wildcard_string("config[v1.0].data") + 'config\\\\[v1\\\\.0\\\\]\\\\.data' + >>> to_regex_safe_wildcard_string("*_file_*.txt") + '.*_file_.*\\\\.txt' + """ + return re.escape(pattern).replace("\\*", ".*") + + +def ensure_dir(dir_path: str): + """Ensure that a directory exists by creating it and all needed parent directories + + Args: + dir_path: Full path to local directory + """ + logger = get_logger(__name__) + + if not os.path.exists(dir_path): + # Try to create the directory + try: + logger.info(f"Creating directory {dir_path}") + os.makedirs(dir_path) + except OSError as exc: + # Only complain if the directory wasn't created + if not os.path.exists(dir_path): + raise DirectoryNotCreatedError(dir_path=dir_path) + else: + logger.warning(str(exc)) + + if not os.path.isdir(dir_path): + # This is very unlikely to happen + raise DirectoryNotCreatedError(dir_path=dir_path) + + +def cleanpath(path: str, *extra: str) -> str: + """Return the same path, normalized and using only front slashes. + + Args: + path: String absolute or relative path. + *extra: Zero or more string arguments representing extra bits to + add to input path in given order. + + Returns: + str: Path that is the product of all input parameters joined. + + Examples: + >>> cleanpath('c:\\dev\\my_root', 'my_dir', 'my_file.ma') + 'c:/dev/my_root/my_dir/my_file.ma' + >>> cleanpath('/Users//smith/folder1/file1.txt') + '/Users/smith/folder1/file1.txt' + >>> cleanpath('C:/temp/some_dir/', '/some_folder/') + 'C:/temp/some_dir/some_folder' + >>> cleanpath('/Applications', '/\\some_app') + '/Applications/some_app' + >>> cleanpath('D:', 'MIM_Files') + 'D:/MIM_Files' + >>> cleanpath('D:\\\\', 'MIM_Files') + 'D:/MIM_Files' + >>> cleanpath('') + '' + >>> cleanpath('', 'blah', 'blah') + 'blah/blah' + >>> cleanpath('/path/to/dir/') + '/path/to/dir' + >>> cleanpath('/path/./to/../file.txt') + '/path/file.txt' + """ + # Add slash if first argument is a drive + # (os.path.join will not add one in this case) + if path.endswith(":"): + path += "/" + # Must strip any leading slashes from extra bits + extras = [] + for ext in extra: + extras.append(ext.lstrip("/\\")) + result = os.path.join(path, *extras) + if not result: + return "" + return os.path.normpath(result).replace("\\", "/") + + +def abspath(basepath: str, relpath: str) -> str: + """Return absolute path of a base path plus a path relative to it. + + Args: + basepath: Absolute file path. + relpath: Input path that is relative to provided base path. + + Examples: + >>> abspath('c:/temp', 'dir/myfile.txt') + 'C:/temp/dir/myfile.txt' + >>> abspath('', 'c:/temp/dir/myfile.txt') + 'C:/temp/dir/myfile.txt' + >>> abspath('c:/some_folder', '../temp/dir/other_dir') + 'C:/temp/dir/other_dir' + >>> abspath('c:/temp/dir2/folder', '../../dir/myfile.txt') + 'C:/temp/dir/myfile.txt' + """ + return cleanpath(os.path.realpath(os.path.join(basepath, relpath))) + + +def relpath(basepath: str, targetpath: str) -> str: + r"""Return the target path as a relative path to base path. + Paths *must* be on same drive. + + Args: + basepath: Absolute directory path that return path will be relative to. + targetpath: Absolute path whose relative value will be returned. + + Examples: + >>> relpath('c:/temp', 'c:/temp/dir/myfile.txt') + 'dir/myfile.txt' + >>> relpath('c:/some_folder', 'c:/temp/dir/other_dir') + '../temp/dir/other_dir' + >>> relpath('c:/temp/dir2/folder', 'C:/temp/dir/myfile.txt') + '../../dir/myfile.txt' + >>> relpath(r'c:/temp\dir2/folder', r'C:/temp\dir\myfile.txt') # noqa: W605 + '../../dir/myfile.txt' + """ + return cleanpath(os.path.relpath(targetpath, basepath)) + + +def is_sub_directory(root_path: str, full_path: str) -> bool: + """Return True if the full path provided begins with the root path. + + Args: + root_path: A root directory. + full_path: A full path to a directory or file. + + Examples: + >>> is_sub_directory('c:/temp', 'C:/temp/dir/myfile.txt') + True + >>> is_sub_directory('', 'c:/temp/dir/myfile.txt') + False + >>> is_sub_directory('c:/some_folder', 'c:/temp/dir/other_dir') + False + >>> is_sub_directory('c:/temp/dir2/folder', '') + False + """ + if not root_path or not full_path: + return False + abs_root = abspath("", root_path) + abs_full = abspath("", full_path) + try: + # Use commonpath to properly check if full_path is under root_path + # If commonpath of both equals the root, then full is under root + return os.path.commonpath([abs_root]) == os.path.commonpath( + [abs_root, abs_full] + ) + except ValueError: + # Raised when paths are on different drives (Windows) or one is relative + return False + + +def fileext(filepath: str): + """Return extension of given file path without the dot and in lower case. + Examples: + >>> fileext('c:/temp/file.txt') + 'txt' + >>> fileext('dir/another_dir/file.PNG') + 'png' + >>> fileext('dir/another_dir') + '' + >>> fileext('dir/another.dir/folder') + '' + >>> fileext('file.backup.tar.gz') + 'gz' + """ + filename = os.path.basename(filepath) + if "." not in filename: + return "" + return os.path.splitext(filename)[-1].strip(".").lower() + + +def mimetype(ext: str): + """Return the mimetype of the given file extension. + + Args: + ext: A file extension which may or may not be preceded by a '.'. + A file path is also accepted. + + Returns: + String mimetype, or blank string if extension is not recognized. + + Examples: + >>> mimetype('jpg') + 'image/jpeg' + >>> mimetype('.jpeg') + 'image/jpeg' + >>> mimetype('c:/temp/my_image.jpg') + 'image/jpeg' + >>> mimetype('.not_a_recognized_file_type') + '' + """ + if "." in ext and not ext.startswith("."): + ext = os.path.splitext(ext)[1] + else: + ext = "." + ext.strip(".") + try: + return mimetypes.types_map[ext] + except KeyError: + return "" + + +@trace +def download_file(url, local_filename): + """Download a file from the passed in URL and save it in the specified location. + Create any folders as necessary. + + Args: + url: The remote URL to download + local_filename: The full path to the local destination file + + Raises: + DirctoryNotCreatedError: If we couldn't create the folder for the download + """ + # Check if path exists, and create it if it doesn't + local_dir = os.path.dirname(local_filename) + + # This can raise an exception + ensure_dir(local_dir) + + try: + with urllib.request.urlopen(url) as r: # nosec B210 + try: + with open(local_filename, "wb") as f: + while True: + chunk = r.read(8192) + if not chunk: + break + f.write(chunk) + except Exception as exc: # pylint: disable=broad-except + msg = f"Failed to write to local file: {local_filename}" + traceback.print_exc() + raise FlowError(msg) from exc + except urllib.error.HTTPError as exc: + msg = f'HTTP error occurred while accessing url "{url}". {exc}' + traceback.print_exc() + raise FlowError(msg) from exc + except urllib.error.URLError as exc: + msg = f'Request to get url "{url}" failed. {exc}' + traceback.print_exc() + raise FlowError(msg) from exc diff --git a/python/tk-core/requirements/3.10/frozen_requirements.txt b/python/tk-core/requirements/3.10/frozen_requirements.txt index 589c0f0a..0ed675c7 100644 --- a/python/tk-core/requirements/3.10/frozen_requirements.txt +++ b/python/tk-core/requirements/3.10/frozen_requirements.txt @@ -1,5 +1,7 @@ distro==1.4.0 +Fileseq==2.1.2 packaging==25.0 +PyJWT==2.12.0 PyYAML==6.0.1 ruamel.yaml==0.18.14 ruamel.yaml.clib==0.2.15 diff --git a/python/tk-core/requirements/3.10/pkgs.zip b/python/tk-core/requirements/3.10/pkgs.zip index 4811a07c..a375cc0d 100644 Binary files a/python/tk-core/requirements/3.10/pkgs.zip and b/python/tk-core/requirements/3.10/pkgs.zip differ diff --git a/python/tk-core/requirements/3.10/requirements.txt b/python/tk-core/requirements/3.10/requirements.txt index eed0486f..fc0667fa 100644 --- a/python/tk-core/requirements/3.10/requirements.txt +++ b/python/tk-core/requirements/3.10/requirements.txt @@ -1,5 +1,7 @@ distro==1.4.0 +fileseq==2.1.2 packaging==25.0 +pyjwt==2.12.0 pyyaml==6.0.1 ruamel.yaml==0.18.14 shotgun_api3==3.10.0 diff --git a/python/tk-core/requirements/3.11/frozen_requirements.txt b/python/tk-core/requirements/3.11/frozen_requirements.txt index 284e721c..c12f38dc 100644 --- a/python/tk-core/requirements/3.11/frozen_requirements.txt +++ b/python/tk-core/requirements/3.11/frozen_requirements.txt @@ -1,5 +1,7 @@ distro==1.9.0 +Fileseq==2.1.2 packaging==25.0 +PyJWT==2.12.0 PyYAML==6.0.2 ruamel.yaml==0.18.14 ruamel.yaml.clib==0.2.15 diff --git a/python/tk-core/requirements/3.11/pkgs.zip b/python/tk-core/requirements/3.11/pkgs.zip index 4d502a0e..ad363b23 100644 Binary files a/python/tk-core/requirements/3.11/pkgs.zip and b/python/tk-core/requirements/3.11/pkgs.zip differ diff --git a/python/tk-core/requirements/3.11/requirements.txt b/python/tk-core/requirements/3.11/requirements.txt index 4c705e43..d03588ab 100644 --- a/python/tk-core/requirements/3.11/requirements.txt +++ b/python/tk-core/requirements/3.11/requirements.txt @@ -1,5 +1,7 @@ distro==1.9.0 +fileseq==2.1.2 packaging==25.0 +pyjwt==2.12.0 pyyaml==6.0.2 ruamel.yaml==0.18.14 shotgun_api3==3.10.0 diff --git a/python/tk-core/requirements/3.13/frozen_requirements.txt b/python/tk-core/requirements/3.13/frozen_requirements.txt index 284e721c..c12f38dc 100644 --- a/python/tk-core/requirements/3.13/frozen_requirements.txt +++ b/python/tk-core/requirements/3.13/frozen_requirements.txt @@ -1,5 +1,7 @@ distro==1.9.0 +Fileseq==2.1.2 packaging==25.0 +PyJWT==2.12.0 PyYAML==6.0.2 ruamel.yaml==0.18.14 ruamel.yaml.clib==0.2.15 diff --git a/python/tk-core/requirements/3.13/pkgs.zip b/python/tk-core/requirements/3.13/pkgs.zip index 78a1b8f1..57c488af 100644 Binary files a/python/tk-core/requirements/3.13/pkgs.zip and b/python/tk-core/requirements/3.13/pkgs.zip differ diff --git a/python/tk-core/requirements/3.13/requirements.txt b/python/tk-core/requirements/3.13/requirements.txt index 4c705e43..d03588ab 100644 --- a/python/tk-core/requirements/3.13/requirements.txt +++ b/python/tk-core/requirements/3.13/requirements.txt @@ -1,5 +1,7 @@ distro==1.9.0 +fileseq==2.1.2 packaging==25.0 +pyjwt==2.12.0 pyyaml==6.0.2 ruamel.yaml==0.18.14 shotgun_api3==3.10.0 diff --git a/python/tk-core/requirements/3.7/certs/shotgun_api3/lib/certifi/cacert.pem b/python/tk-core/requirements/3.7/certs/shotgun_api3/lib/certifi/cacert.pem deleted file mode 100644 index 64c05d7f..00000000 --- a/python/tk-core/requirements/3.7/certs/shotgun_api3/lib/certifi/cacert.pem +++ /dev/null @@ -1,4778 +0,0 @@ - -# Issuer: CN=Entrust Root Certification Authority O=Entrust, Inc. OU=www.entrust.net/CPS is incorporated by reference/(c) 2006 Entrust, Inc. -# Subject: CN=Entrust Root Certification Authority O=Entrust, Inc. OU=www.entrust.net/CPS is incorporated by reference/(c) 2006 Entrust, Inc. -# Label: "Entrust Root Certification Authority" -# Serial: 1164660820 -# MD5 Fingerprint: d6:a5:c3:ed:5d:dd:3e:00:c1:3d:87:92:1f:1d:3f:e4 -# SHA1 Fingerprint: b3:1e:b1:b7:40:e3:6c:84:02:da:dc:37:d4:4d:f5:d4:67:49:52:f9 -# SHA256 Fingerprint: 73:c1:76:43:4f:1b:c6:d5:ad:f4:5b:0e:76:e7:27:28:7c:8d:e5:76:16:c1:e6:e6:14:1a:2b:2c:bc:7d:8e:4c ------BEGIN CERTIFICATE----- -MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMC -VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0 -Lm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW -KGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsGA1UEAxMkRW50cnVzdCBSb290IENl -cnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0MloXDTI2MTEyNzIw -NTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMTkw -NwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSBy -ZWZlcmVuY2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNV -BAMTJEVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJ -KoZIhvcNAQEBBQADggEPADCCAQoCggEBALaVtkNC+sZtKm9I35RMOVcF7sN5EUFo -Nu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYszA9u3g3s+IIRe7bJWKKf4 -4LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOwwCj0Yzfv9 -KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGI -rb68j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi -94DkZfs0Nw4pgHBNrziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOB -sDCBrTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAi -gA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1MzQyWjAfBgNVHSMEGDAWgBRo -kORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DHhmak8fdLQ/uE -vW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA -A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9t -O1KzKtvn1ISMY/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6Zua -AGAT/3B+XxFNSRuzFVJ7yVTav52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP -9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTSW3iDVuycNsMm4hH2Z0kdkquM++v/ -eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0tHuu2guQOHXvgR1m -0vdXcDazv/wor3ElhVsT/h5/WrQ8 ------END CERTIFICATE----- - -# Issuer: CN=QuoVadis Root CA 2 O=QuoVadis Limited -# Subject: CN=QuoVadis Root CA 2 O=QuoVadis Limited -# Label: "QuoVadis Root CA 2" -# Serial: 1289 -# MD5 Fingerprint: 5e:39:7b:dd:f8:ba:ec:82:e9:ac:62:ba:0c:54:00:2b -# SHA1 Fingerprint: ca:3a:fb:cf:12:40:36:4b:44:b2:16:20:88:80:48:39:19:93:7c:f7 -# SHA256 Fingerprint: 85:a0:dd:7d:d7:20:ad:b7:ff:05:f8:3d:54:2b:20:9d:c7:ff:45:28:f7:d6:77:b1:83:89:fe:a5:e5:c4:9e:86 ------BEGIN CERTIFICATE----- -MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x -GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv -b3QgQ0EgMjAeFw0wNjExMjQxODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNV -BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W -YWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCa -GMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6XJxg -Fyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55J -WpzmM+Yklvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bB -rrcCaoF6qUWD4gXmuVbBlDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp -+ARz8un+XJiM9XOva7R+zdRcAitMOeGylZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1 -ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt66/3FsvbzSUr5R/7mp/i -Ucw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1JdxnwQ5hYIiz -PtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og -/zOhD7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UH -oycR7hYQe7xFSkyyBNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuI -yV77zGHcizN300QyNQliBJIWENieJ0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1Ud -EwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBQahGK8SEwzJQTU7tD2 -A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGUa6FJpEcwRTEL -MAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT -ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2f -BluornFdLwUvZ+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzn -g/iN/Ae42l9NLmeyhP3ZRPx3UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2Bl -fF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodmVjB3pjd4M1IQWK4/YY7yarHvGH5K -WWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK+JDSV6IZUaUtl0Ha -B0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrWIozc -hLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPR -TUIZ3Ph1WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWD -mbA4CD/pXvk1B+TJYm5Xf6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0Z -ohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y -4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8VCLAAVBpQ570su9t+Oza -8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u ------END CERTIFICATE----- - -# Issuer: CN=QuoVadis Root CA 3 O=QuoVadis Limited -# Subject: CN=QuoVadis Root CA 3 O=QuoVadis Limited -# Label: "QuoVadis Root CA 3" -# Serial: 1478 -# MD5 Fingerprint: 31:85:3c:62:94:97:63:b9:aa:fd:89:4e:af:6f:e0:cf -# SHA1 Fingerprint: 1f:49:14:f7:d8:74:95:1d:dd:ae:02:c0:be:fd:3a:2d:82:75:51:85 -# SHA256 Fingerprint: 18:f1:fc:7f:20:5d:f8:ad:dd:eb:7f:e0:07:dd:57:e3:af:37:5a:9c:4d:8d:73:54:6b:f4:f1:fe:d1:e1:8d:35 ------BEGIN CERTIFICATE----- -MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x -GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv -b3QgQ0EgMzAeFw0wNjExMjQxOTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNV -BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W -YWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDM -V0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNggDhoB -4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUr -H556VOijKTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd -8lyyBTNvijbO0BNO/79KDDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9Cabwv -vWhDFlaJKjdhkf2mrk7AyxRllDdLkgbvBNDInIjbC3uBr7E9KsRlOni27tyAsdLT -mZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwpp5ijJUMv7/FfJuGITfhe -btfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8nT8KKdjc -T5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDt -WAEXMJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZ -c6tsgLjoC2SToJyMGf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A -4iLItLRkT9a6fUg+qGkM17uGcclzuD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYD -VR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHTBgkrBgEEAb5YAAMwgcUwgZMG -CCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmljYXRlIGNvbnN0 -aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0 -aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVu -dC4wLQYIKwYBBQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2Nw -czALBgNVHQ8EBAMCAQYwHQYDVR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4G -A1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4ywLQoUmkRzBFMQswCQYDVQQGEwJC -TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UEAxMSUXVvVmFkaXMg -Um9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZVqyM0 -7ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSem -d1o417+shvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd -+LJ2w/w4E6oM3kJpK27zPOuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B -4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadN -t54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp8kokUvd0/bpO5qgdAm6x -DYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBCbjPsMZ57 -k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6s -zHXug/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0j -Wy10QJLZYxkNc91pvGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeT -mJlglFwjz1onl14LBQaTNx47aTbrqZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK -4SVhM7JZG+Ju1zdXtg2pEto= ------END CERTIFICATE----- - -# Issuer: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com -# Subject: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com -# Label: "DigiCert Assured ID Root CA" -# Serial: 17154717934120587862167794914071425081 -# MD5 Fingerprint: 87:ce:0b:7b:2a:0e:49:00:e1:58:71:9b:37:a8:93:72 -# SHA1 Fingerprint: 05:63:b8:63:0d:62:d7:5a:bb:c8:ab:1e:4b:df:b5:a8:99:b2:4d:43 -# SHA256 Fingerprint: 3e:90:99:b5:01:5e:8f:48:6c:00:bc:ea:9d:11:1e:e7:21:fa:ba:35:5a:89:bc:f1:df:69:56:1e:3d:c6:32:5c ------BEGIN CERTIFICATE----- -MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv -b3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQswCQYDVQQG -EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl -cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwggEi -MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7c -JpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYP -mDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+ -wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4 -VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1roV9Iq4/ -AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whfGHdPAgMB -AAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW -BBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYun -pyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRC -dWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTf -fwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+fT8r87cm -NW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5QZ7dsvfPx -H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe -+o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g== ------END CERTIFICATE----- - -# Issuer: CN=DigiCert Global Root CA O=DigiCert Inc OU=www.digicert.com -# Subject: CN=DigiCert Global Root CA O=DigiCert Inc OU=www.digicert.com -# Label: "DigiCert Global Root CA" -# Serial: 10944719598952040374951832963794454346 -# MD5 Fingerprint: 79:e4:a9:84:0d:7d:3a:96:d7:c0:4f:e2:43:4c:89:2e -# SHA1 Fingerprint: a8:98:5d:3a:65:e5:e5:c4:b2:d7:d6:6d:40:c6:dd:2f:b1:9c:54:36 -# SHA256 Fingerprint: 43:48:a0:e9:44:4c:78:cb:26:5e:05:8d:5e:89:44:b4:d8:4f:96:62:bd:26:db:25:7f:89:34:a4:43:c7:01:61 ------BEGIN CERTIFICATE----- -MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD -QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT -MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j -b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG -9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB -CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 -nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt -43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P -T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 -gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO -BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR -TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw -DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr -hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg -06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF -PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls -YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk -CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= ------END CERTIFICATE----- - -# Issuer: CN=DigiCert High Assurance EV Root CA O=DigiCert Inc OU=www.digicert.com -# Subject: CN=DigiCert High Assurance EV Root CA O=DigiCert Inc OU=www.digicert.com -# Label: "DigiCert High Assurance EV Root CA" -# Serial: 3553400076410547919724730734378100087 -# MD5 Fingerprint: d4:74:de:57:5c:39:b2:d3:9c:85:83:c5:c0:65:49:8a -# SHA1 Fingerprint: 5f:b7:ee:06:33:e2:59:db:ad:0c:4c:9a:e6:d3:8f:1a:61:c7:dc:25 -# SHA256 Fingerprint: 74:31:e5:f4:c3:c1:ce:46:90:77:4f:0b:61:e0:54:40:88:3b:a9:a0:1e:d0:0b:a6:ab:d7:80:6e:d3:b1:18:cf ------BEGIN CERTIFICATE----- -MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j -ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL -MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 -LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug -RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm -+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW -PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM -xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB -Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3 -hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg -EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF -MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA -FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec -nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z -eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF -hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2 -Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe -vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep -+OkuE6N36B9K ------END CERTIFICATE----- - -# Issuer: CN=SwissSign Gold CA - G2 O=SwissSign AG -# Subject: CN=SwissSign Gold CA - G2 O=SwissSign AG -# Label: "SwissSign Gold CA - G2" -# Serial: 13492815561806991280 -# MD5 Fingerprint: 24:77:d9:a8:91:d1:3b:fa:88:2d:c2:ff:f8:cd:33:93 -# SHA1 Fingerprint: d8:c5:38:8a:b7:30:1b:1b:6e:d4:7a:e6:45:25:3a:6f:9f:1a:27:61 -# SHA256 Fingerprint: 62:dd:0b:e9:b9:f5:0a:16:3e:a0:f8:e7:5c:05:3b:1e:ca:57:ea:55:c8:68:8f:64:7c:68:81:f2:c8:35:7b:95 ------BEGIN CERTIFICATE----- -MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV -BAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2ln -biBHb2xkIENBIC0gRzIwHhcNMDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBF -MQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dpc3NTaWduIEFHMR8wHQYDVQQDExZT -d2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC -CgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUqt2/8 -76LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+ -bbqBHH5CjCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c -6bM8K8vzARO/Ws/BtQpgvd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqE -emA8atufK+ze3gE/bk3lUIbLtK/tREDFylqM2tIrfKjuvqblCqoOpd8FUrdVxyJd -MmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvRAiTysybUa9oEVeXBCsdt -MDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuendjIj3o02y -MszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69y -FGkOpeUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPi -aG59je883WX0XaxR7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxM -gI93e2CaHt+28kgeDrpOVG2Y4OGiGqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCB -qTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUWyV7 -lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64OfPAeGZe6Drn -8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov -L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe6 -45R88a7A3hfm5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczO -UYrHUDFu4Up+GC9pWbY9ZIEr44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5 -O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOfMke6UiI0HTJ6CVanfCU2qT1L2sCC -bwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6mGu6uLftIdxf+u+yv -GPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxpmo/a -77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCC -hdiDyyJkvC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid3 -92qgQmwLOM7XdVAyksLfKzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEpp -Ld6leNcG2mqeSz53OiATIgHQv2ieY2BrNU0LbbqhPcCT4H8js1WtciVORvnSFu+w -ZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6LqjviOvrv1vA+ACOzB2+htt -Qc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ ------END CERTIFICATE----- - -# Issuer: CN=SecureTrust CA O=SecureTrust Corporation -# Subject: CN=SecureTrust CA O=SecureTrust Corporation -# Label: "SecureTrust CA" -# Serial: 17199774589125277788362757014266862032 -# MD5 Fingerprint: dc:32:c3:a7:6d:25:57:c7:68:09:9d:ea:2d:a9:a2:d1 -# SHA1 Fingerprint: 87:82:c6:c3:04:35:3b:cf:d2:96:92:d2:59:3e:7d:44:d9:34:ff:11 -# SHA256 Fingerprint: f1:c1:b5:0a:e5:a2:0d:d8:03:0e:c9:f6:bc:24:82:3d:d3:67:b5:25:57:59:b4:e7:1b:61:fc:e9:f7:37:5d:73 ------BEGIN CERTIFICATE----- -MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBI -MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x -FzAVBgNVBAMTDlNlY3VyZVRydXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIz -MTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAeBgNVBAoTF1NlY3VyZVRydXN0IENv -cnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCCASIwDQYJKoZIhvcN -AQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQXOZEz -Zum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO -0gMdA+9tDWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIao -wW8xQmxSPmjL8xk037uHGFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj -7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b01k/unK8RCSc43Oz969XL0Imnal0ugBS -8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmHursCAwEAAaOBnTCBmjAT -BgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB -/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCeg -JYYjaHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGC -NxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt3 -6Z3q059c4EVlew3KW+JwULKUBRSuSceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/ -3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHfmbx8IVQr5Fiiu1cprp6poxkm -D5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZnMUFdAvnZyPS -CPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR -3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE= ------END CERTIFICATE----- - -# Issuer: CN=Secure Global CA O=SecureTrust Corporation -# Subject: CN=Secure Global CA O=SecureTrust Corporation -# Label: "Secure Global CA" -# Serial: 9751836167731051554232119481456978597 -# MD5 Fingerprint: cf:f4:27:0d:d4:ed:dc:65:16:49:6d:3d:da:bf:6e:de -# SHA1 Fingerprint: 3a:44:73:5a:e5:81:90:1f:24:86:61:46:1e:3b:9c:c4:5f:f5:3a:1b -# SHA256 Fingerprint: 42:00:f5:04:3a:c8:59:0e:bb:52:7d:20:9e:d1:50:30:29:fb:cb:d4:1c:a1:b5:06:ec:27:f1:5a:de:7d:ac:69 ------BEGIN CERTIFICATE----- -MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBK -MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x -GTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkx -MjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3Qg -Q29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwggEiMA0GCSqG -SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jxYDiJ -iQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa -/FHtaMbQbqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJ -jnIFHovdRIWCQtBJwB1g8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnI -HmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYVHDGA76oYa8J719rO+TMg1fW9ajMtgQT7 -sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi0XPnj3pDAgMBAAGjgZ0w -gZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQF -MAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCsw -KaAnoCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsG -AQQBgjcVAQQDAgEAMA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0L -URYD7xh8yOOvaliTFGCRsoTciE6+OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXO -H0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cnCDpOGR86p1hcF895P4vkp9Mm -I50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/53CYNv6ZHdAbY -iNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc -f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW ------END CERTIFICATE----- - -# Issuer: CN=COMODO Certification Authority O=COMODO CA Limited -# Subject: CN=COMODO Certification Authority O=COMODO CA Limited -# Label: "COMODO Certification Authority" -# Serial: 104350513648249232941998508985834464573 -# MD5 Fingerprint: 5c:48:dc:f7:42:72:ec:56:94:6d:1c:cc:71:35:80:75 -# SHA1 Fingerprint: 66:31:bf:9e:f7:4f:9e:b6:c9:d5:a6:0c:ba:6a:be:d1:f7:bd:ef:7b -# SHA256 Fingerprint: 0c:2c:d6:3d:f7:80:6f:a3:99:ed:e8:09:11:6b:57:5b:f8:79:89:f0:65:18:f9:80:8c:86:05:03:17:8b:af:66 ------BEGIN CERTIFICATE----- -MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCB -gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G -A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV -BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEyMDEwMDAw -MDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3Jl -YXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01P -RE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0 -aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3 -UcEbVASY06m/weaKXTuH+7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI -2GqGd0S7WWaXUF601CxwRM/aN5VCaTwwxHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8 -Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV4EajcNxo2f8ESIl33rXp -+2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA1KGzqSX+ -DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5O -nKVIrLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW -/zAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6g -PKA6hjhodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9u -QXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOCAQEAPpiem/Yb6dc5t3iuHXIY -SdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CPOGEIqB6BCsAv -IC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/ -RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4 -zJVSk/BwJVmcIGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5dd -BA6+C4OmF4O5MBKgxTMVBbkN+8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IB -ZQ== ------END CERTIFICATE----- - -# Issuer: CN=COMODO ECC Certification Authority O=COMODO CA Limited -# Subject: CN=COMODO ECC Certification Authority O=COMODO CA Limited -# Label: "COMODO ECC Certification Authority" -# Serial: 41578283867086692638256921589707938090 -# MD5 Fingerprint: 7c:62:ff:74:9d:31:53:5e:68:4a:d5:78:aa:1e:bf:23 -# SHA1 Fingerprint: 9f:74:4e:9f:2b:4d:ba:ec:0f:31:2c:50:b6:56:3b:8e:2d:93:c3:11 -# SHA256 Fingerprint: 17:93:92:7a:06:14:54:97:89:ad:ce:2f:8f:34:f7:f0:b6:6d:0f:3a:e3:a3:b8:4d:21:ec:15:db:ba:4f:ad:c7 ------BEGIN CERTIFICATE----- -MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTEL -MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE -BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMT -IkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwMzA2MDAw -MDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdy -ZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09N -T0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlv -biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSR -FtSrYpn1PlILBs5BAH+X4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0J -cfRK9ChQtP6IHG4/bC8vCVlbpVsLM5niwz2J+Wos77LTBumjQjBAMB0GA1UdDgQW -BBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ -BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VGFAkK+qDm -fQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv -GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= ------END CERTIFICATE----- - -# Issuer: CN=Certigna O=Dhimyotis -# Subject: CN=Certigna O=Dhimyotis -# Label: "Certigna" -# Serial: 18364802974209362175 -# MD5 Fingerprint: ab:57:a6:5b:7d:42:82:19:b5:d8:58:26:28:5e:fd:ff -# SHA1 Fingerprint: b1:2e:13:63:45:86:a4:6f:1a:b2:60:68:37:58:2d:c4:ac:fd:94:97 -# SHA256 Fingerprint: e3:b6:a2:db:2e:d7:ce:48:84:2f:7a:c5:32:41:c7:b7:1d:54:14:4b:fb:40:c1:1f:3f:1d:0b:42:f5:ee:a1:2d ------BEGIN CERTIFICATE----- -MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNV -BAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4X -DTA3MDYyOTE1MTMwNVoXDTI3MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQ -BgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwIQ2VydGlnbmEwggEiMA0GCSqGSIb3 -DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7qXOEm7RFHYeGifBZ4 -QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyHGxny -gQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbw -zBfsV1/pogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q -130yGLMLLGq/jj8UEYkgDncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2 -JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKfIrjxwo1p3Po6WAbfAgMBAAGjgbwwgbkw -DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQtCRZvgHyUtVF9lo53BEw -ZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJBgNVBAYT -AkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzj -AQ/JSP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG -9w0BAQUFAAOCAQEAhQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8h -bV6lUmPOEvjvKtpv6zf+EwLHyzs+ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFnc -fca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1kluPBS1xp81HlDQwY9qcEQCYsuu -HWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY1gkIl2PlwS6w -t0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw -WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== ------END CERTIFICATE----- - -# Issuer: O=Chunghwa Telecom Co., Ltd. OU=ePKI Root Certification Authority -# Subject: O=Chunghwa Telecom Co., Ltd. OU=ePKI Root Certification Authority -# Label: "ePKI Root Certification Authority" -# Serial: 28956088682735189655030529057352760477 -# MD5 Fingerprint: 1b:2e:00:ca:26:06:90:3d:ad:fe:6f:15:68:d3:6b:b3 -# SHA1 Fingerprint: 67:65:0d:f1:7e:8e:7e:5b:82:40:a4:f4:56:4b:cf:e2:3d:69:c6:f0 -# SHA256 Fingerprint: c0:a6:f4:dc:63:a2:4b:fd:cf:54:ef:2a:6a:08:2a:0a:72:de:35:80:3e:2f:f5:ff:52:7a:e5:d8:72:06:df:d5 ------BEGIN CERTIFICATE----- -MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBe -MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0 -ZC4xKjAoBgNVBAsMIWVQS0kgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe -Fw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMxMjdaMF4xCzAJBgNVBAYTAlRXMSMw -IQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEqMCgGA1UECwwhZVBL -SSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEF -AAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAH -SyZbCUNsIZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAh -ijHyl3SJCRImHJ7K2RKilTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3X -DZoTM1PRYfl61dd4s5oz9wCGzh1NlDivqOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1 -TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX12ruOzjjK9SXDrkb5wdJ -fzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0OWQqraffA -sgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uU -WH1+ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLS -nT0IFaUQAS2zMnaolQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pH -dmX2Os+PYhcZewoozRrSgx4hxyy/vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJip -NiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXiZo1jDiVN1Rmy5nk3pyKdVDEC -AwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/QkqiMAwGA1UdEwQF -MAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH -ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGB -uvl2ICO1J2B01GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6Yl -PwZpVnPDimZI+ymBV3QGypzqKOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkP -JXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdVxrsStZf0X4OFunHB2WyBEXYKCrC/ -gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEPNXubrjlpC2JgQCA2 -j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+rGNm6 -5ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUB -o2M3IUxExJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS -/jQ6fbjpKdx2qcgw+BRxgMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2z -Gp1iro2C6pSe3VkQw63d4k3jMdXH7OjysP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTE -W9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmODBCEIZ43ygknQW/2xzQ+D -hNQ+IIX3Sj0rnP0qCglN6oH4EZw= ------END CERTIFICATE----- - -# Issuer: O=certSIGN OU=certSIGN ROOT CA -# Subject: O=certSIGN OU=certSIGN ROOT CA -# Label: "certSIGN ROOT CA" -# Serial: 35210227249154 -# MD5 Fingerprint: 18:98:c0:d6:e9:3a:fc:f9:b0:f5:0c:f7:4b:01:44:17 -# SHA1 Fingerprint: fa:b7:ee:36:97:26:62:fb:2d:b0:2a:f6:bf:03:fd:e8:7c:4b:2f:9b -# SHA256 Fingerprint: ea:a9:62:c4:fa:4a:6b:af:eb:e4:15:19:6d:35:1c:cd:88:8d:4f:53:f3:fa:8a:e6:d7:c4:66:a9:4e:60:42:bb ------BEGIN CERTIFICATE----- -MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYT -AlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBD -QTAeFw0wNjA3MDQxNzIwMDRaFw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJP -MREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTCC -ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7IJUqOtdu0KBuqV5Do -0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHHrfAQ -UySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5d -RdY4zTW2ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQ -OA7+j0xbm0bqQfWwCHTD0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwv -JoIQ4uNllAoEwF73XVv4EOLQunpL+943AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08C -AwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAcYwHQYDVR0O -BBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IBAQA+0hyJ -LjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecY -MnQ8SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ -44gx+FkagQnIl6Z0x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6I -Jd1hJyMctTEHBDa0GpC9oHRxUIltvBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNw -i/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7NzTogVZ96edhBiIL5VaZVDADlN -9u6wWk5JRFRYX0KD ------END CERTIFICATE----- - -# Issuer: CN=NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny O=NetLock Kft. OU=Tan\xfas\xedtv\xe1nykiad\xf3k (Certification Services) -# Subject: CN=NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny O=NetLock Kft. OU=Tan\xfas\xedtv\xe1nykiad\xf3k (Certification Services) -# Label: "NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny" -# Serial: 80544274841616 -# MD5 Fingerprint: c5:a1:b7:ff:73:dd:d6:d7:34:32:18:df:fc:3c:ad:88 -# SHA1 Fingerprint: 06:08:3f:59:3f:15:a1:04:a0:69:a4:6b:a9:03:d0:06:b7:97:09:91 -# SHA256 Fingerprint: 6c:61:da:c3:a2:de:f0:31:50:6b:e0:36:d2:a6:fe:40:19:94:fb:d1:3d:f9:c8:d4:66:59:92:74:c4:46:ec:98 ------BEGIN CERTIFICATE----- -MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQG -EwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3 -MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNl -cnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBBcmFueSAoQ2xhc3MgR29sZCkgRsWR -dGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgxMjA2MTUwODIxWjCB -pzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxOZXRM -b2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlm -aWNhdGlvbiBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNz -IEdvbGQpIEbFkXRhbsO6c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A -MIIBCgKCAQEAxCRec75LbRTDofTjl5Bu0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrT -lF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw/HpYzY6b7cNGbIRwXdrz -AZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAkH3B5r9s5 -VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRG -ILdwfzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2 -BJtr+UBdADTHLpl1neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAG -AQH/AgEEMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2M -U9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwWqZw8UQCgwBEIBaeZ5m8BiFRh -bvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTtaYtOUZcTh5m2C -+C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC -bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2F -uLjbvrW5KfnaNwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2 -XjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= ------END CERTIFICATE----- - -# Issuer: CN=Microsec e-Szigno Root CA 2009 O=Microsec Ltd. -# Subject: CN=Microsec e-Szigno Root CA 2009 O=Microsec Ltd. -# Label: "Microsec e-Szigno Root CA 2009" -# Serial: 14014712776195784473 -# MD5 Fingerprint: f8:49:f4:03:bc:44:2d:83:be:48:69:7d:29:64:fc:b1 -# SHA1 Fingerprint: 89:df:74:fe:5c:f4:0f:4a:80:f9:e3:37:7d:54:da:91:e1:01:31:8e -# SHA256 Fingerprint: 3c:5f:81:fe:a5:fa:b8:2c:64:bf:a2:ea:ec:af:cd:e8:e0:77:fc:86:20:a7:ca:e5:37:16:3d:f3:6e:db:f3:78 ------BEGIN CERTIFICATE----- -MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYD -VQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0 -ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0G -CSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTAeFw0wOTA2MTYxMTMwMThaFw0y -OTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3Qx -FjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3pp -Z25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o -dTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvP -kd6mJviZpWNwrZuuyjNAfW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tc -cbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG0IMZfcChEhyVbUr02MelTTMuhTlAdX4U -fIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKApxn1ntxVUwOXewdI/5n7 -N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm1HxdrtbC -xkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1 -+rUCAwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G -A1UdDgQWBBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPM -Pcu1SCOhGnqmKrs0aDAbBgNVHREEFDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqG -SIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0olZMEyL/azXm4Q5DwpL7v8u8h -mLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfXI/OMn74dseGk -ddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775 -tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c -2Pm2G2JwCz02yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5t -HMN1Rq41Bab2XD0h7lbwyYIiLXpUq3DDfSJlgnCW ------END CERTIFICATE----- - -# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3 -# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3 -# Label: "GlobalSign Root CA - R3" -# Serial: 4835703278459759426209954 -# MD5 Fingerprint: c5:df:b8:49:ca:05:13:55:ee:2d:ba:1a:c3:3e:b0:28 -# SHA1 Fingerprint: d6:9b:56:11:48:f0:1c:77:c5:45:78:c1:09:26:df:5b:85:69:76:ad -# SHA256 Fingerprint: cb:b5:22:d7:b7:f1:27:ad:6a:01:13:86:5b:df:1c:d4:10:2e:7d:07:59:af:63:5a:7c:f4:72:0d:c9:63:c5:3b ------BEGIN CERTIFICATE----- -MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G -A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp -Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4 -MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG -A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI -hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8 -RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT -gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm -KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd -QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ -XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw -DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o -LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU -RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp -jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK -6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX -mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs -Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH -WD9f ------END CERTIFICATE----- - -# Issuer: CN=Izenpe.com O=IZENPE S.A. -# Subject: CN=Izenpe.com O=IZENPE S.A. -# Label: "Izenpe.com" -# Serial: 917563065490389241595536686991402621 -# MD5 Fingerprint: a6:b0:cd:85:80:da:5c:50:34:a3:39:90:2f:55:67:73 -# SHA1 Fingerprint: 2f:78:3d:25:52:18:a7:4a:65:39:71:b5:2c:a2:9c:45:15:6f:e9:19 -# SHA256 Fingerprint: 25:30:cc:8e:98:32:15:02:ba:d9:6f:9b:1f:ba:1b:09:9e:2d:29:9e:0f:45:48:bb:91:4f:36:3b:c0:d4:53:1f ------BEGIN CERTIFICATE----- -MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4 -MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6 -ZW5wZS5jb20wHhcNMDcxMjEzMTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYD -VQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5j -b20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ03rKDx6sp4boFmVq -scIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAKClaO -xdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6H -LmYRY2xU+zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFX -uaOKmMPsOzTFlUFpfnXCPCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQD -yCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxTOTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+ -JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbKF7jJeodWLBoBHmy+E60Q -rLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK0GqfvEyN -BjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8L -hij+0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIB -QFqNeb+Lz0vPqhbBleStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+ -HMh3/1uaD7euBUbl8agW7EekFwIDAQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2lu -Zm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+SVpFTlBFIFMuQS4gLSBDSUYg -QTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBGNjIgUzgxQzBB -BgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx -MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC -AQYwHQYDVR0OBBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUA -A4ICAQB4pgwWSp9MiDrAyw6lFn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWb -laQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbgakEyrkgPH7UIBzg/YsfqikuFgba56 -awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8qhT/AQKM6WfxZSzwo -JNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Csg1lw -LDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCT -VyvehQP5aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGk -LhObNA5me0mrZJfQRsN5nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJb -UjWumDqtujWTI6cfSN01RpiyEGjkpTHCClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/ -QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZoQ0iy2+tzJOeRf1SktoA+ -naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1ZWrOZyGls -QyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw== ------END CERTIFICATE----- - -# Issuer: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc. -# Subject: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc. -# Label: "Go Daddy Root Certificate Authority - G2" -# Serial: 0 -# MD5 Fingerprint: 80:3a:bc:22:c1:e6:fb:8d:9b:3b:27:4a:32:1b:9a:01 -# SHA1 Fingerprint: 47:be:ab:c9:22:ea:e8:0e:78:78:34:62:a7:9f:45:c2:54:fd:e6:8b -# SHA256 Fingerprint: 45:14:0b:32:47:eb:9c:c8:c5:b4:f0:d7:b5:30:91:f7:32:92:08:9e:6e:5a:63:e2:74:9d:d3:ac:a9:19:8e:da ------BEGIN CERTIFICATE----- -MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx -EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT -EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp -ZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIz -NTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH -EwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8GA1UE -AxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIw -DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKD -E6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH -/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7Rnwy -DfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlRJQVh -GkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGR -tDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEA -AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE -FDqahQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmX -WWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu -9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95kTXWXwTr -gIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo -2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO -LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI -4uJEvlz36hz1 ------END CERTIFICATE----- - -# Issuer: CN=Starfield Root Certificate Authority - G2 O=Starfield Technologies, Inc. -# Subject: CN=Starfield Root Certificate Authority - G2 O=Starfield Technologies, Inc. -# Label: "Starfield Root Certificate Authority - G2" -# Serial: 0 -# MD5 Fingerprint: d6:39:81:c6:52:7e:96:69:fc:fc:ca:66:ed:05:f2:96 -# SHA1 Fingerprint: b5:1c:06:7c:ee:2b:0c:3d:f8:55:ab:2d:92:f4:fe:39:d4:e7:0f:0e -# SHA256 Fingerprint: 2c:e1:cb:0b:f9:d2:f9:e1:02:99:3f:be:21:51:52:c3:b2:dd:0c:ab:de:1c:68:e5:31:9b:83:91:54:db:b7:f5 ------BEGIN CERTIFICATE----- -MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx -EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT -HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs -ZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAw -MFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6 -b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQgVGVj -aG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZp -Y2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC -ggEBAL3twQP89o/8ArFvW59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMg -nLRJdzIpVv257IzdIvpy3Cdhl+72WoTsbhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1 -HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNkN3mSwOxGXn/hbVNMYq/N -Hwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7NfZTD4p7dN -dloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0 -HZbUJtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO -BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0G -CSqGSIb3DQEBCwUAA4IBAQARWfolTwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjU -sHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx4mcujJUDJi5DnUox9g61DLu3 -4jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUwF5okxBDgBPfg -8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K -pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1 -mMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0 ------END CERTIFICATE----- - -# Issuer: CN=Starfield Services Root Certificate Authority - G2 O=Starfield Technologies, Inc. -# Subject: CN=Starfield Services Root Certificate Authority - G2 O=Starfield Technologies, Inc. -# Label: "Starfield Services Root Certificate Authority - G2" -# Serial: 0 -# MD5 Fingerprint: 17:35:74:af:7b:61:1c:eb:f4:f9:3c:e2:ee:40:f9:a2 -# SHA1 Fingerprint: 92:5a:8f:8d:2c:6d:04:e0:66:5f:59:6a:ff:22:d8:63:e8:25:6f:3f -# SHA256 Fingerprint: 56:8d:69:05:a2:c8:87:08:a4:b3:02:51:90:ed:cf:ed:b1:97:4a:60:6a:13:c6:e5:29:0f:cb:2a:e6:3e:da:b5 ------BEGIN CERTIFICATE----- -MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMx -EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT -HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVs -ZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5 -MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNVBAYTAlVTMRAwDgYD -VQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFy -ZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2Vy -dmljZXMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI -hvcNAQEBBQADggEPADCCAQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20p -OsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm2 -8xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4PahHQUw2eeBGg6345AWh1K -Ts9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLPLJGmpufe -hRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk -6mFBrMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAw -DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+q -AdcwKziIorhtSpzyEZGDMA0GCSqGSIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMI -bw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPPE95Dz+I0swSdHynVv/heyNXB -ve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTyxQGjhdByPq1z -qwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd -iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn -0q23KXB56jzaYyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCN -sSi6 ------END CERTIFICATE----- - -# Issuer: CN=AffirmTrust Commercial O=AffirmTrust -# Subject: CN=AffirmTrust Commercial O=AffirmTrust -# Label: "AffirmTrust Commercial" -# Serial: 8608355977964138876 -# MD5 Fingerprint: 82:92:ba:5b:ef:cd:8a:6f:a6:3d:55:f9:84:f6:d6:b7 -# SHA1 Fingerprint: f9:b5:b6:32:45:5f:9c:be:ec:57:5f:80:dc:e9:6e:2c:c7:b2:78:b7 -# SHA256 Fingerprint: 03:76:ab:1d:54:c5:f9:80:3c:e4:b2:e2:01:a0:ee:7e:ef:7b:57:b6:36:e8:a9:3c:9b:8d:48:60:c9:6f:5f:a7 ------BEGIN CERTIFICATE----- -MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UE -BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz -dCBDb21tZXJjaWFsMB4XDTEwMDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDEL -MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp -cm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC -AQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6EqdbDuKP -Hx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yr -ba0F8PrVC8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPAL -MeIrJmqbTFeurCA+ukV6BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1 -yHp52UKqK39c/s4mT6NmgTWvRLpUHhwwMmWd5jyTXlBOeuM61G7MGvv50jeuJCqr -VwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNVHQ4EFgQUnZPGU4teyq8/ -nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ -KoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYG -XUPGhi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNj -vbz4YYCanrHOQnDiqX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivt -Z8SOyUOyXGsViQK8YvxO8rUzqrJv0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9g -N53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0khsUlHRUe072o0EclNmsxZt9YC -nlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8= ------END CERTIFICATE----- - -# Issuer: CN=AffirmTrust Networking O=AffirmTrust -# Subject: CN=AffirmTrust Networking O=AffirmTrust -# Label: "AffirmTrust Networking" -# Serial: 8957382827206547757 -# MD5 Fingerprint: 42:65:ca:be:01:9a:9a:4c:a9:8c:41:49:cd:c0:d5:7f -# SHA1 Fingerprint: 29:36:21:02:8b:20:ed:02:f5:66:c5:32:d1:d6:ed:90:9f:45:00:2f -# SHA256 Fingerprint: 0a:81:ec:5a:92:97:77:f1:45:90:4a:f3:8d:5d:50:9f:66:b5:e2:c5:8f:cd:b5:31:05:8b:0e:17:f3:f0:b4:1b ------BEGIN CERTIFICATE----- -MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UE -BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz -dCBOZXR3b3JraW5nMB4XDTEwMDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDEL -MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp -cm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC -AQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SEHi3y -YJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbua -kCNrmreIdIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRL -QESxG9fhwoXA3hA/Pe24/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp -6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gbh+0t+nvujArjqWaJGctB+d1ENmHP4ndG -yH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNVHQ4EFgQUBx/S55zawm6i -QLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ -KoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfO -tDIuUFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzu -QY0x2+c06lkh1QF612S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZ -Lgo/bNjR9eUJtGxUAArgFU2HdW23WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4u -olu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9/ZFvgrG+CJPbFEfxojfHRZ48 -x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s= ------END CERTIFICATE----- - -# Issuer: CN=AffirmTrust Premium O=AffirmTrust -# Subject: CN=AffirmTrust Premium O=AffirmTrust -# Label: "AffirmTrust Premium" -# Serial: 7893706540734352110 -# MD5 Fingerprint: c4:5d:0e:48:b6:ac:28:30:4e:0a:bc:f9:38:16:87:57 -# SHA1 Fingerprint: d8:a6:33:2c:e0:03:6f:b1:85:f6:63:4f:7d:6a:06:65:26:32:28:27 -# SHA256 Fingerprint: 70:a7:3f:7f:37:6b:60:07:42:48:90:45:34:b1:14:82:d5:bf:0e:69:8e:cc:49:8d:f5:25:77:eb:f2:e9:3b:9a ------BEGIN CERTIFICATE----- -MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UE -BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVz -dCBQcmVtaXVtMB4XDTEwMDEyOTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkG -A1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1U -cnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxBLf -qV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtnBKAQ -JG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ -+jjeRFcV5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrS -s8PhaJyJ+HoAVt70VZVs+7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5 -HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmdGPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d7 -70O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5Rp9EixAqnOEhss/n/fauG -V+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NIS+LI+H+S -qHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S -5u046uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4Ia -C1nEWTJ3s7xgaVY5/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TX -OwF0lkLgAOIua+rF7nKsu7/+6qqo+Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYE -FJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ -BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByvMiPIs0laUZx2 -KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg -Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B -8OWycvpEgjNC6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQ -MKSOyARiqcTtNd56l+0OOF6SL5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc -0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK+4w1IX2COPKpVJEZNZOUbWo6xbLQ -u4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmVBtWVyuEklut89pMF -u+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFgIxpH -YoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8 -GKa1qF60g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaO -RtGdFNrHF+QFlozEJLUbzxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6e -KeC2uAloGRwYQw== ------END CERTIFICATE----- - -# Issuer: CN=AffirmTrust Premium ECC O=AffirmTrust -# Subject: CN=AffirmTrust Premium ECC O=AffirmTrust -# Label: "AffirmTrust Premium ECC" -# Serial: 8401224907861490260 -# MD5 Fingerprint: 64:b0:09:55:cf:b1:d5:99:e2:be:13:ab:a6:5d:ea:4d -# SHA1 Fingerprint: b8:23:6b:00:2f:1d:16:86:53:01:55:6c:11:a4:37:ca:eb:ff:c3:bb -# SHA256 Fingerprint: bd:71:fd:f6:da:97:e4:cf:62:d1:64:7a:dd:25:81:b0:7d:79:ad:f8:39:7e:b4:ec:ba:9c:5e:84:88:82:14:23 ------BEGIN CERTIFICATE----- -MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMC -VVMxFDASBgNVBAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQ -cmVtaXVtIEVDQzAeFw0xMDAxMjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJ -BgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1UcnVzdDEgMB4GA1UEAwwXQWZmaXJt -VHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQNMF4bFZ0D -0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQN8O9 -ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0G -A1UdDgQWBBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4G -A1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/Vs -aobgxCd05DhT1wV/GzTjxi+zygk8N53X57hG8f2h4nECMEJZh0PUUd+60wkyWs6I -flc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKMeQ== ------END CERTIFICATE----- - -# Issuer: CN=Certum Trusted Network CA O=Unizeto Technologies S.A. OU=Certum Certification Authority -# Subject: CN=Certum Trusted Network CA O=Unizeto Technologies S.A. OU=Certum Certification Authority -# Label: "Certum Trusted Network CA" -# Serial: 279744 -# MD5 Fingerprint: d5:e9:81:40:c5:18:69:fc:46:2c:89:75:62:0f:aa:78 -# SHA1 Fingerprint: 07:e0:32:e0:20:b7:2c:3f:19:2f:06:28:a2:59:3a:19:a7:0f:06:9e -# SHA256 Fingerprint: 5c:58:46:8d:55:f5:8e:49:7e:74:39:82:d2:b5:00:10:b6:d1:65:37:4a:cf:83:a7:d4:a3:2d:b7:68:c4:40:8e ------BEGIN CERTIFICATE----- -MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBM -MSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5D -ZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBU -cnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIyMTIwNzM3WhcNMjkxMjMxMTIwNzM3 -WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMg -Uy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSIw -IAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0B -AQEFAAOCAQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rH -UV+rpDKmYYe2bg+G0jACl/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LM -TXPb865Px1bVWqeWifrzq2jUI4ZZJ88JJ7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVU -BBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4fOQtf/WsX+sWn7Et0brM -kUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0cvW0QM8x -AcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNV -HRMBAf8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNV -HQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15y -sHhE49wcrwn9I0j6vSrEuVUEtRCjjSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfL -I9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1mS1FhIrlQgnXdAIv94nYmem8 -J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5ajZt3hrvJBW8qY -VoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI -03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw= ------END CERTIFICATE----- - -# Issuer: CN=TWCA Root Certification Authority O=TAIWAN-CA OU=Root CA -# Subject: CN=TWCA Root Certification Authority O=TAIWAN-CA OU=Root CA -# Label: "TWCA Root Certification Authority" -# Serial: 1 -# MD5 Fingerprint: aa:08:8f:f6:f9:7b:b7:f2:b1:a7:1e:9b:ea:ea:bd:79 -# SHA1 Fingerprint: cf:9e:87:6d:d3:eb:fc:42:26:97:a3:b5:a3:7a:a0:76:a9:06:23:48 -# SHA256 Fingerprint: bf:d8:8f:e1:10:1c:41:ae:3e:80:1b:f8:be:56:35:0e:e9:ba:d1:a6:b9:bd:51:5e:dc:5c:6d:5b:87:11:ac:44 ------BEGIN CERTIFICATE----- -MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzES -MBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFU -V0NBIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMz -WhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJVEFJV0FO -LUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlm -aWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB -AQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFE -AcK0HMMxQhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HH -K3XLfJ+utdGdIzdjp9xCoi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeX -RfwZVzsrb+RH9JlF/h3x+JejiB03HFyP4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/z -rX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1ry+UPizgN7gr8/g+YnzAx -3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV -HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkq -hkiG9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeC -MErJk/9q56YAf4lCmtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdls -XebQ79NqZp4VKIV66IIArB6nCWlWQtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62D -lhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVYT0bf+215WfKEIlKuD8z7fDvn -aspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocnyYh0igzyXxfkZ -YiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw== ------END CERTIFICATE----- - -# Issuer: O=SECOM Trust Systems CO.,LTD. OU=Security Communication RootCA2 -# Subject: O=SECOM Trust Systems CO.,LTD. OU=Security Communication RootCA2 -# Label: "Security Communication RootCA2" -# Serial: 0 -# MD5 Fingerprint: 6c:39:7d:a4:0e:55:59:b2:3f:d6:41:b1:12:50:de:43 -# SHA1 Fingerprint: 5f:3b:8c:f2:f8:10:b3:7d:78:b4:ce:ec:19:19:c3:73:34:b9:c7:74 -# SHA256 Fingerprint: 51:3b:2c:ec:b8:10:d4:cd:e5:dd:85:39:1a:df:c6:c2:dd:60:d8:7b:b7:36:d2:b5:21:48:4a:a4:7a:0e:be:f6 ------BEGIN CERTIFICATE----- -MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDEl -MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMe -U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoX -DTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRy -dXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmlj -YXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANAV -OVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGr -zbl+dp+++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVM -VAX3NuRFg3sUZdbcDE3R3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQ -hNBqyjoGADdH5H5XTz+L62e4iKrFvlNVspHEfbmwhRkGeC7bYRr6hfVKkaHnFtWO -ojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1KEOtOghY6rCcMU/Gt1SSw -awNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8QIH4D5cs -OPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 -DQEBCwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpF -coJxDjrSzG+ntKEju/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXc -okgfGT+Ok+vx+hfuzU7jBBJV1uXk3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8 -t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6qtnRGEmyR7jTV7JqR50S+kDFy -1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29mvVXIwAHIRc/ -SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03 ------END CERTIFICATE----- - -# Issuer: CN=Actalis Authentication Root CA O=Actalis S.p.A./03358520967 -# Subject: CN=Actalis Authentication Root CA O=Actalis S.p.A./03358520967 -# Label: "Actalis Authentication Root CA" -# Serial: 6271844772424770508 -# MD5 Fingerprint: 69:c1:0d:4f:07:a3:1b:c3:fe:56:3d:04:bc:11:f6:a6 -# SHA1 Fingerprint: f3:73:b3:87:06:5a:28:84:8a:f2:f3:4a:ce:19:2b:dd:c7:8e:9c:ac -# SHA256 Fingerprint: 55:92:60:84:ec:96:3a:64:b9:6e:2a:be:01:ce:0b:a8:6a:64:fb:fe:bc:c7:aa:b5:af:c1:55:b3:7f:d7:60:66 ------BEGIN CERTIFICATE----- -MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UE -BhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8w -MzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290 -IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDkyMjExMjIwMlowazELMAkGA1UEBhMC -SVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1 -ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENB -MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNv -UTufClrJwkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX -4ay8IMKx4INRimlNAJZaby/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9 -KK3giq0itFZljoZUj5NDKd45RnijMCO6zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/ -gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1fYVEiVRvjRuPjPdA1Yprb -rxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2oxgkg4YQ -51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2F -be8lEfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxe -KF+w6D9Fz8+vm2/7hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4F -v6MGn8i1zeQf1xcGDXqVdFUNaBr8EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbn -fpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5jF66CyCU3nuDuP/jVo23Eek7 -jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLYiDrIn3hm7Ynz -ezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt -ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAL -e3KHwGCmSUyIWOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70 -jsNjLiNmsGe+b7bAEzlgqqI0JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDz -WochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKxK3JCaKygvU5a2hi/a5iB0P2avl4V -SM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+Xlff1ANATIGk0k9j -pwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC4yyX -X04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+Ok -fcvHlXHo2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7R -K4X9p2jIugErsWx0Hbhzlefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btU -ZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXemOR/qnuOf0GZvBeyqdn6/axag67XH/JJU -LysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9vwGYT7JZVEc+NHt4bVaT -LnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg== ------END CERTIFICATE----- - -# Issuer: CN=Buypass Class 2 Root CA O=Buypass AS-983163327 -# Subject: CN=Buypass Class 2 Root CA O=Buypass AS-983163327 -# Label: "Buypass Class 2 Root CA" -# Serial: 2 -# MD5 Fingerprint: 46:a7:d2:fe:45:fb:64:5a:a8:59:90:9b:78:44:9b:29 -# SHA1 Fingerprint: 49:0a:75:74:de:87:0a:47:fe:58:ee:f6:c7:6b:eb:c6:0b:12:40:99 -# SHA256 Fingerprint: 9a:11:40:25:19:7c:5b:b9:5d:94:e6:3d:55:cd:43:79:08:47:b6:46:b2:3c:df:11:ad:a4:a0:0e:ff:15:fb:48 ------BEGIN CERTIFICATE----- -MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd -MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg -Q2xhc3MgMiBSb290IENBMB4XDTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1ow -TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw -HgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB -BQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1g1Lr -6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPV -L4O2fuPn9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC91 -1K2GScuVr1QGbNgGE41b/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHx -MlAQTn/0hpPshNOOvEu/XAFOBz3cFIqUCqTqc/sLUegTBxj6DvEr0VQVfTzh97QZ -QmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeffawrbD02TTqigzXsu8lkB -arcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgIzRFo1clr -Us3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLi -FRhnBkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRS -P/TizPJhk9H9Z2vXUq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN -9SG9dKpN6nIDSdvHXx1iY8f93ZHsM+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxP -AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMmAd+BikoL1Rpzz -uvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAU18h -9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s -A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3t -OluwlN5E40EIosHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo -+fsicdl9sz1Gv7SEr5AcD48Saq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7 -KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYdDnkM/crqJIByw5c/8nerQyIKx+u2 -DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWDLfJ6v9r9jv6ly0Us -H8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0oyLQ -I+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK7 -5t98biGCwWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h -3PFaTWwyI0PurKju7koSCTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPz -Y11aWOIv4x3kqdbQCtCev9eBCfHJxyYNrJgWVqA= ------END CERTIFICATE----- - -# Issuer: CN=Buypass Class 3 Root CA O=Buypass AS-983163327 -# Subject: CN=Buypass Class 3 Root CA O=Buypass AS-983163327 -# Label: "Buypass Class 3 Root CA" -# Serial: 2 -# MD5 Fingerprint: 3d:3b:18:9e:2c:64:5a:e8:d5:88:ce:0e:f9:37:c2:ec -# SHA1 Fingerprint: da:fa:f7:fa:66:84:ec:06:8f:14:50:bd:c7:c2:81:a5:bc:a9:64:57 -# SHA256 Fingerprint: ed:f7:eb:bc:a2:7a:2a:38:4d:38:7b:7d:40:10:c6:66:e2:ed:b4:84:3e:4c:29:b4:ae:1d:5b:93:32:e6:b2:4d ------BEGIN CERTIFICATE----- -MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd -MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg -Q2xhc3MgMyBSb290IENBMB4XDTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFow -TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw -HgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB -BQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRHsJ8Y -ZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3E -N3coTRiR5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9 -tznDDgFHmV0ST9tD+leh7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX -0DJq1l1sDPGzbjniazEuOQAnFN44wOwZZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c -/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH2xc519woe2v1n/MuwU8X -KhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV/afmiSTY -zIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvS -O1UQRwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D -34xFMFbG02SrZvPAXpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgP -K9Dx2hzLabjKSWJtyNBjYt1gD1iqj6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3 -AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFEe4zf/lb+74suwv -Tg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAACAj -QTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV -cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXS -IGrs/CIBKM+GuIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2 -HJLw5QY33KbmkJs4j1xrG0aGQ0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsa -O5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8ZORK15FTAaggiG6cX0S5y2CBNOxv -033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2KSb12tjE8nVhz36u -dmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz6MkE -kbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg41 -3OEMXbugUZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvD -u79leNKGef9JOxqDDPDeeOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq -4/g7u9xN12TyUb7mqqta6THuBrxzvxNiCp/HuZc= ------END CERTIFICATE----- - -# Issuer: CN=T-TeleSec GlobalRoot Class 3 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center -# Subject: CN=T-TeleSec GlobalRoot Class 3 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center -# Label: "T-TeleSec GlobalRoot Class 3" -# Serial: 1 -# MD5 Fingerprint: ca:fb:40:a8:4e:39:92:8a:1d:fe:8e:2f:c4:27:ea:ef -# SHA1 Fingerprint: 55:a6:72:3e:cb:f2:ec:cd:c3:23:74:70:19:9d:2a:be:11:e3:81:d1 -# SHA256 Fingerprint: fd:73:da:d3:1c:64:4f:f1:b4:3b:ef:0c:cd:da:96:71:0b:9c:d9:87:5e:ca:7e:31:70:7a:f3:e9:6d:52:2b:bd ------BEGIN CERTIFICATE----- -MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx -KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd -BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl -YyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgxMDAxMTAyOTU2WhcNMzMxMDAxMjM1 -OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy -aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50 -ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0G -CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN -8ELg63iIVl6bmlQdTQyK9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/ -RLyTPWGrTs0NvvAgJ1gORH8EGoel15YUNpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4 -hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZFiP0Zf3WHHx+xGwpzJFu5 -ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W0eDrXltM -EnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGj -QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1 -A/d2O2GCahKqGFPrAyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOy -WL6ukK2YJ5f+AbGwUgC4TeQbIXQbfsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ -1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzTucpH9sry9uetuUg/vBa3wW30 -6gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7hP0HHRwA11fXT -91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml -e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4p -TpPDpFQUWw== ------END CERTIFICATE----- - -# Issuer: CN=D-TRUST Root Class 3 CA 2 2009 O=D-Trust GmbH -# Subject: CN=D-TRUST Root Class 3 CA 2 2009 O=D-Trust GmbH -# Label: "D-TRUST Root Class 3 CA 2 2009" -# Serial: 623603 -# MD5 Fingerprint: cd:e0:25:69:8d:47:ac:9c:89:35:90:f7:fd:51:3d:2f -# SHA1 Fingerprint: 58:e8:ab:b0:36:15:33:fb:80:f7:9b:1b:6d:29:d3:ff:8d:5f:00:f0 -# SHA256 Fingerprint: 49:e7:a4:42:ac:f0:ea:62:87:05:00:54:b5:25:64:b6:50:e4:f4:9e:42:e3:48:d6:aa:38:e0:39:e9:57:b1:c1 ------BEGIN CERTIFICATE----- -MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRF -MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBD -bGFzcyAzIENBIDIgMjAwOTAeFw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NTha -ME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMM -HkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIwDQYJKoZIhvcNAQEB -BQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOADER03 -UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42 -tSHKXzlABF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9R -ySPocq60vFYJfxLLHLGvKZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsM -lFqVlNpQmvH/pStmMaTJOKDfHR+4CS7zp+hnUquVH+BGPtikw8paxTGA6Eian5Rp -/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUCAwEAAaOCARowggEWMA8G -A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ4PGEMA4G -A1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVj -dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUy -MENBJTIwMiUyMDIwMDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRl -cmV2b2NhdGlvbmxpc3QwQ6BBoD+GPWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3Js -L2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAwOS5jcmwwDQYJKoZIhvcNAQEL -BQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm2H6NMLVwMeni -acfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0 -o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4K -zCUqNQT4YJEVdT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8 -PIWmawomDeCTmGCufsYkl4phX5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3Y -Johw1+qRzT65ysCQblrGXnRl11z+o+I= ------END CERTIFICATE----- - -# Issuer: CN=D-TRUST Root Class 3 CA 2 EV 2009 O=D-Trust GmbH -# Subject: CN=D-TRUST Root Class 3 CA 2 EV 2009 O=D-Trust GmbH -# Label: "D-TRUST Root Class 3 CA 2 EV 2009" -# Serial: 623604 -# MD5 Fingerprint: aa:c6:43:2c:5e:2d:cd:c4:34:c0:50:4f:11:02:4f:b6 -# SHA1 Fingerprint: 96:c9:1b:0b:95:b4:10:98:42:fa:d0:d8:22:79:fe:60:fa:b9:16:83 -# SHA256 Fingerprint: ee:c5:49:6b:98:8c:e9:86:25:b9:34:09:2e:ec:29:08:be:d0:b0:f3:16:c2:d4:73:0c:84:ea:f1:f3:d3:48:81 ------BEGIN CERTIFICATE----- -MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRF -MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBD -bGFzcyAzIENBIDIgRVYgMjAwOTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUw -NDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNV -BAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAwOTCCASIwDQYJKoZI -hvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfSegpn -ljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM0 -3TP1YtHhzRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6Z -qQTMFexgaDbtCHu39b+T7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lR -p75mpoo6Kr3HGrHhFPC+Oh25z1uxav60sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8 -HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure3511H3a6UCAwEAAaOCASQw -ggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyvcop9Ntea -HNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFw -Oi8vZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xh -c3MlMjAzJTIwQ0ElMjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1E -RT9jZXJ0aWZpY2F0ZXJldm9jYXRpb25saXN0MEagRKBChkBodHRwOi8vd3d3LmQt -dHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xhc3NfM19jYV8yX2V2XzIwMDku -Y3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+PPoeUSbrh/Yp -3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05 -nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNF -CSuGdXzfX2lXANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7na -xpeG0ILD5EJt/rDiZE4OJudANCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqX -KVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVvw9y4AyHqnxbxLFS1 ------END CERTIFICATE----- - -# Issuer: CN=CA Disig Root R2 O=Disig a.s. -# Subject: CN=CA Disig Root R2 O=Disig a.s. -# Label: "CA Disig Root R2" -# Serial: 10572350602393338211 -# MD5 Fingerprint: 26:01:fb:d8:27:a7:17:9a:45:54:38:1a:43:01:3b:03 -# SHA1 Fingerprint: b5:61:eb:ea:a4:de:e4:25:4b:69:1a:98:a5:57:47:c2:34:c7:d9:71 -# SHA256 Fingerprint: e2:3d:4a:03:6d:7b:70:e9:f5:95:b1:42:20:79:d2:b9:1e:df:bb:1f:b6:51:a0:63:3e:aa:8a:9d:c5:f8:07:03 ------BEGIN CERTIFICATE----- -MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNV -BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu -MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQy -MDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmEx -EzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERpc2lnIFJvb3QgUjIw -ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbCw3Oe -NcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNH -PWSb6WiaxswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3I -x2ymrdMxp7zo5eFm1tL7A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbe -QTg06ov80egEFGEtQX6sx3dOy1FU+16SGBsEWmjGycT6txOgmLcRK7fWV8x8nhfR -yyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqVg8NTEQxzHQuyRpDRQjrO -QG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa5Beny912 -H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJ -QfYEkoopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUD -i/ZnWejBBhG93c+AAk9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORs -nLMOPReisjQS1n6yqEm70XooQL6iFh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1 -rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud -DwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5uQu0wDQYJKoZI -hvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM -tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqf -GopTpti72TVVsRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkb -lvdhuDvEK7Z4bLQjb/D907JedR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka -+elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W81k/BfDxujRNt+3vrMNDcTa/F1bal -TFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjxmHHEt38OFdAlab0i -nSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01utI3 -gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18Dr -G5gPcFw0sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3Os -zMOl6W8KjptlwlCFtaOgUxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8x -L4ysEr3vQCj8KWefshNPZiTEUxnpHikV7+ZtsH8tZ/3zbBt1RqPlShfppNcL ------END CERTIFICATE----- - -# Issuer: CN=ACCVRAIZ1 O=ACCV OU=PKIACCV -# Subject: CN=ACCVRAIZ1 O=ACCV OU=PKIACCV -# Label: "ACCVRAIZ1" -# Serial: 6828503384748696800 -# MD5 Fingerprint: d0:a0:5a:ee:05:b6:09:94:21:a1:7d:f1:b2:29:82:02 -# SHA1 Fingerprint: 93:05:7a:88:15:c6:4f:ce:88:2f:fa:91:16:52:28:78:bc:53:64:17 -# SHA256 Fingerprint: 9a:6e:c0:12:e1:a7:da:9d:be:34:19:4d:47:8a:d7:c0:db:18:22:fb:07:1d:f1:29:81:49:6e:d1:04:38:41:13 ------BEGIN CERTIFICATE----- -MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UE -AwwJQUNDVlJBSVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQsw -CQYDVQQGEwJFUzAeFw0xMTA1MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQ -BgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwHUEtJQUNDVjENMAsGA1UECgwEQUND -VjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCb -qau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gMjmoY -HtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWo -G2ioPej0RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpA -lHPrzg5XPAOBOp0KoVdDaaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhr -IA8wKFSVf+DuzgpmndFALW4ir50awQUZ0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/ -0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDGWuzndN9wrqODJerWx5eH -k6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs78yM2x/47 -4KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMO -m3WR5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpa -cXpkatcnYGMN285J9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPl -uUsXQA+xtrn13k/c4LOsOxFwYIRKQ26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYI -KwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRwOi8vd3d3LmFjY3YuZXMvZmls -ZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEuY3J0MB8GCCsG -AQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2 -VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeT -VfZW6oHlNsyMHj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIG -CCsGAQUFBwICMIIBFB6CARAAQQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUA -cgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBhAO0AegAgAGQAZQAgAGwAYQAgAEEA -QwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUAYwBuAG8AbABvAGcA -7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBjAHQA -cgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAA -QwBQAFMAIABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUA -czAwBggrBgEFBQcCARYkaHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2Mu -aHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRt -aW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2MV9kZXIuY3JsMA4GA1Ud -DwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZIhvcNAQEF -BQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdp -D70ER9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gU -JyCpZET/LtZ1qmxNYEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+m -AM/EKXMRNt6GGT6d7hmKG9Ww7Y49nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepD -vV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJTS+xJlsndQAJxGJ3KQhfnlms -tn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3sCPdK6jT2iWH -7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h -I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szA -h1xA2syVP1XgNce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xF -d3+YJ5oyXSrjhO7FmGYvliAd3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2H -pPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3pEfbRD0tVNEYqi4Y7 ------END CERTIFICATE----- - -# Issuer: CN=TWCA Global Root CA O=TAIWAN-CA OU=Root CA -# Subject: CN=TWCA Global Root CA O=TAIWAN-CA OU=Root CA -# Label: "TWCA Global Root CA" -# Serial: 3262 -# MD5 Fingerprint: f9:03:7e:cf:e6:9e:3c:73:7a:2a:90:07:69:ff:2b:96 -# SHA1 Fingerprint: 9c:bb:48:53:f6:a4:f6:d3:52:a4:e8:32:52:55:60:13:f5:ad:af:65 -# SHA256 Fingerprint: 59:76:90:07:f7:68:5d:0f:cd:50:87:2f:9f:95:d5:75:5a:5b:2b:45:7d:81:f3:69:2b:61:0a:98:67:2f:0e:1b ------BEGIN CERTIFICATE----- -MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcx -EjAQBgNVBAoTCVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMT -VFdDQSBHbG9iYWwgUm9vdCBDQTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5 -NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQKEwlUQUlXQU4tQ0ExEDAOBgNVBAsT -B1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3QgQ0EwggIiMA0GCSqG -SIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2CnJfF -10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz -0ALfUPZVr2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfCh -MBwqoJimFb3u/Rk28OKRQ4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbH -zIh1HrtsBv+baz4X7GGqcXzGHaL3SekVtTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc -46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1WKKD+u4ZqyPpcC1jcxkt2 -yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99sy2sbZCi -laLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYP -oA/pyJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQA -BDzfuBSO6N+pjWxnkjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcE -qYSjMq+u7msXi7Kx/mzhkIyIqJdIzshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm -4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB -/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6gcFGn90xHNcgL -1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn -LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WF -H6vPNOw/KP4M8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNo -RI2T9GRwoD2dKAXDOXC4Ynsg/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+ -nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlglPx4mI88k1HtQJAH32RjJMtOcQWh -15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryPA9gK8kxkRr05YuWW -6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3mi4TW -nsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5j -wa19hAM8EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWz -aGHQRiapIVJpLesux+t3zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmy -KwbQBM0= ------END CERTIFICATE----- - -# Issuer: CN=TeliaSonera Root CA v1 O=TeliaSonera -# Subject: CN=TeliaSonera Root CA v1 O=TeliaSonera -# Label: "TeliaSonera Root CA v1" -# Serial: 199041966741090107964904287217786801558 -# MD5 Fingerprint: 37:41:49:1b:18:56:9a:26:f5:ad:c2:66:fb:40:a5:4c -# SHA1 Fingerprint: 43:13:bb:96:f1:d5:86:9b:c1:4e:6a:92:f6:cf:f6:34:69:87:82:37 -# SHA256 Fingerprint: dd:69:36:fe:21:f8:f0:77:c1:23:a1:a5:21:c1:22:24:f7:22:55:b7:3e:03:a7:26:06:93:e8:a2:4b:0f:a3:89 ------BEGIN CERTIFICATE----- -MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAw -NzEUMBIGA1UECgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJv -b3QgQ0EgdjEwHhcNMDcxMDE4MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYD -VQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwWVGVsaWFTb25lcmEgUm9vdCBDQSB2 -MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+6yfwIaPzaSZVfp3F -VRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA3GV1 -7CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+X -Z75Ljo1kB1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+ -/jXh7VB7qTCNGdMJjmhnXb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs -81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxHoLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkm -dtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3F0fUTPHSiXk+TT2YqGHe -Oh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJoWjiUIMu -sDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4 -pgd7gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fs -slESl1MpWtTwEhDcTwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQ -arMCpgKIv7NHfirZ1fpoeDVNAgMBAAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYD -VR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qWDNXr+nuqF+gTEjANBgkqhkiG -9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNmzqjMDfz1mgbl -dxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx -0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1Tj -TQpgcmLNkQfWpb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBed -Y2gea+zDTYa4EzAvXUYNR0PVG6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7 -Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpcc41teyWRyu5FrgZLAMzTsVlQ2jqI -OylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOTJsjrDNYmiLbAJM+7 -vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2qReW -t88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcn -HL/EVlP6Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVx -SK236thZiNSQvxaz2emsWWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY= ------END CERTIFICATE----- - -# Issuer: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center -# Subject: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center -# Label: "T-TeleSec GlobalRoot Class 2" -# Serial: 1 -# MD5 Fingerprint: 2b:9b:9e:e4:7b:6c:1f:00:72:1a:cc:c1:77:79:df:6a -# SHA1 Fingerprint: 59:0d:2d:7d:88:4f:40:2e:61:7e:a5:62:32:17:65:cf:17:d8:94:e9 -# SHA256 Fingerprint: 91:e2:f5:78:8d:58:10:eb:a7:ba:58:73:7d:e1:54:8a:8e:ca:cd:01:45:98:bc:0b:14:3e:04:1b:17:05:25:52 ------BEGIN CERTIFICATE----- -MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx -KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd -BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl -YyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgxMDAxMTA0MDE0WhcNMzMxMDAxMjM1 -OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy -aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50 -ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0G -CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUd -AqSzm1nzHoqvNK38DcLZSBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiC -FoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/FvudocP05l03Sx5iRUKrERLMjfTlH6VJi -1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx9702cu+fjOlbpSD8DT6Iavq -jnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGVWOHAD3bZ -wI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGj -QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/ -WSA2AHmgoCJrjNXyYdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhy -NsZt+U2e+iKo4YFWz827n+qrkRk4r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPAC -uvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNfvNoBYimipidx5joifsFvHZVw -IEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR3p1m0IvVVGb6 -g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN -9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlP -BSeOE6Fuwg== ------END CERTIFICATE----- - -# Issuer: CN=Atos TrustedRoot 2011 O=Atos -# Subject: CN=Atos TrustedRoot 2011 O=Atos -# Label: "Atos TrustedRoot 2011" -# Serial: 6643877497813316402 -# MD5 Fingerprint: ae:b9:c4:32:4b:ac:7f:5d:66:cc:77:94:bb:2a:77:56 -# SHA1 Fingerprint: 2b:b1:f5:3e:55:0c:1d:c5:f1:d4:e6:b7:6a:46:4b:55:06:02:ac:21 -# SHA256 Fingerprint: f3:56:be:a2:44:b7:a9:1e:b3:5d:53:ca:9a:d7:86:4a:ce:01:8e:2d:35:d5:f8:f9:6d:df:68:a6:f4:1a:a4:74 ------BEGIN CERTIFICATE----- -MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UE -AwwVQXRvcyBUcnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQG -EwJERTAeFw0xMTA3MDcxNDU4MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMM -FUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMC -REUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCVhTuXbyo7LjvPpvMp -Nb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr54rM -VD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+ -SZFhyBH+DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ -4J7sVaE3IqKHBAUsR320HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0L -cp2AMBYHlT8oDv3FdU9T1nSatCQujgKRz3bFmx5VdJx4IbHwLfELn8LVlhgf8FQi -eowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7Rl+lwrrw7GWzbITAPBgNV -HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZbNshMBgG -A1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3 -DQEBCwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8j -vZfza1zv7v1Apt+hk6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kP -DpFrdRbhIfzYJsdHt6bPWHJxfrrhTZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pc -maHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a961qn8FYiqTxlVMYVqL2Gns2D -lmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G3mB/ufNPRJLv -KrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed ------END CERTIFICATE----- - -# Issuer: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited -# Subject: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited -# Label: "QuoVadis Root CA 1 G3" -# Serial: 687049649626669250736271037606554624078720034195 -# MD5 Fingerprint: a4:bc:5b:3f:fe:37:9a:fa:64:f0:e2:fa:05:3d:0b:ab -# SHA1 Fingerprint: 1b:8e:ea:57:96:29:1a:c9:39:ea:b8:0a:81:1a:73:73:c0:93:79:67 -# SHA256 Fingerprint: 8a:86:6f:d1:b2:76:b5:7e:57:8e:92:1c:65:82:8a:2b:ed:58:e9:f2:f2:88:05:41:34:b7:f1:f4:bf:c9:cc:74 ------BEGIN CERTIFICATE----- -MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQEL -BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc -BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00 -MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM -aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEgRzMwggIiMA0GCSqG -SIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakEPBtV -wedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWe -rNrwU8lmPNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF341 -68Xfuw6cwI2H44g4hWf6Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh -4Pw5qlPafX7PGglTvF0FBM+hSo+LdoINofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXp -UhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/lg6AnhF4EwfWQvTA9xO+o -abw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV7qJZjqlc -3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/G -KubX9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSt -hfbZxbGL0eUQMk1fiyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KO -Tk0k+17kBL5yG6YnLUlamXrXXAkgt3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOt -zCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB -BjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZIhvcNAQELBQAD -ggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC -MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2 -cDMT/uFPpiN3GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUN -qXsCHKnQO18LwIE6PWThv6ctTr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5 -YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP+V04ikkwj+3x6xn0dxoxGE1nVGwv -b2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh3jRJjehZrJ3ydlo2 -8hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fawx/k -NSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNj -ZgKAvQU6O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhp -q1467HxpvMc7hU6eFbm0FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFt -nh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOVhMJKzRwuJIczYOXD ------END CERTIFICATE----- - -# Issuer: CN=QuoVadis Root CA 2 G3 O=QuoVadis Limited -# Subject: CN=QuoVadis Root CA 2 G3 O=QuoVadis Limited -# Label: "QuoVadis Root CA 2 G3" -# Serial: 390156079458959257446133169266079962026824725800 -# MD5 Fingerprint: af:0c:86:6e:bf:40:2d:7f:0b:3e:12:50:ba:12:3d:06 -# SHA1 Fingerprint: 09:3c:61:f3:8b:8b:dc:7d:55:df:75:38:02:05:00:e1:25:f5:c8:36 -# SHA256 Fingerprint: 8f:e4:fb:0a:f9:3a:4d:0d:67:db:0b:eb:b2:3e:37:c7:1b:f3:25:dc:bc:dd:24:0e:a0:4d:af:58:b4:7e:18:40 ------BEGIN CERTIFICATE----- -MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL -BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc -BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00 -MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM -aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzMwggIiMA0GCSqG -SIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFhZiFf -qq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMW -n4rjyduYNM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ym -c5GQYaYDFCDy54ejiK2toIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+ -O7q414AB+6XrW7PFXmAqMaCvN+ggOp+oMiwMzAkd056OXbxMmO7FGmh77FOm6RQ1 -o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+lV0POKa2Mq1W/xPtbAd0j -IaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZoL1NesNKq -IcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz -8eQQsSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43eh -vNURG3YBZwjgQQvD6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l -7ZizlWNof/k19N+IxWA1ksB8aRxhlRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALG -cC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB -BjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZIhvcNAQELBQAD -ggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 -AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RC -roijQ1h5fq7KpVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0Ga -W/ZZGYjeVYg3UQt4XAoeo0L9x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4n -lv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgzdWqTHBLmYF5vHX/JHyPLhGGfHoJE -+V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6XU/IyAgkwo1jwDQHV -csaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+NwmNtd -dbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNg -KCLjsZWDzYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeM -HVOyToV7BjjHLPj4sHKNJeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4 -WSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M ------END CERTIFICATE----- - -# Issuer: CN=QuoVadis Root CA 3 G3 O=QuoVadis Limited -# Subject: CN=QuoVadis Root CA 3 G3 O=QuoVadis Limited -# Label: "QuoVadis Root CA 3 G3" -# Serial: 268090761170461462463995952157327242137089239581 -# MD5 Fingerprint: df:7d:b9:ad:54:6f:68:a1:df:89:57:03:97:43:b0:d7 -# SHA1 Fingerprint: 48:12:bd:92:3c:a8:c4:39:06:e7:30:6d:27:96:e6:a4:cf:22:2e:7d -# SHA256 Fingerprint: 88:ef:81:de:20:2e:b0:18:45:2e:43:f8:64:72:5c:ea:5f:bd:1f:c2:d9:d2:05:73:07:09:c5:d8:b8:69:0f:46 ------BEGIN CERTIFICATE----- -MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQEL -BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc -BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00 -MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM -aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMgRzMwggIiMA0GCSqG -SIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286IxSR -/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNu -FoM7pmRLMon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXR -U7Ox7sWTaYI+FrUoRqHe6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+c -ra1AdHkrAj80//ogaX3T7mH1urPnMNA3I4ZyYUUpSFlob3emLoG+B01vr87ERROR -FHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3UVDmrJqMz6nWB2i3ND0/k -A9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f75li59wzw -eyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634Ryl -sSqiMd5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBp -VzgeAVuNVejH38DMdyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0Q -A4XN8f+MFrXBsj6IbGB/kE+V9/YtrQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ -ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB -BjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZIhvcNAQELBQAD -ggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px -KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnI -FUBhynLWcKzSt/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5Wvv -oxXqA/4Ti2Tk08HS6IT7SdEQTXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFg -u/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9DuDcpmvJRPpq3t/O5jrFc/ZSXPsoaP -0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGibIh6BJpsQBJFxwAYf -3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmDhPbl -8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+ -DhcI00iX0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HN -PlopNLk9hM6xZdRZkZFWdSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ -ywaZWWDYWGWVjUTR939+J399roD1B0y2PpxxVJkES/1Y+Zj0 ------END CERTIFICATE----- - -# Issuer: CN=DigiCert Assured ID Root G2 O=DigiCert Inc OU=www.digicert.com -# Subject: CN=DigiCert Assured ID Root G2 O=DigiCert Inc OU=www.digicert.com -# Label: "DigiCert Assured ID Root G2" -# Serial: 15385348160840213938643033620894905419 -# MD5 Fingerprint: 92:38:b9:f8:63:24:82:65:2c:57:33:e6:fe:81:8f:9d -# SHA1 Fingerprint: a1:4b:48:d9:43:ee:0a:0e:40:90:4f:3c:e0:a4:c0:91:93:51:5d:3f -# SHA256 Fingerprint: 7d:05:eb:b6:82:33:9f:8c:94:51:ee:09:4e:eb:fe:fa:79:53:a1:14:ed:b2:f4:49:49:45:2f:ab:7d:2f:c1:85 ------BEGIN CERTIFICATE----- -MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBl -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv -b3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQG -EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl -cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwggEi -MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSA -n61UQbVH35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4Htecc -biJVMWWXvdMX0h5i89vqbFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9Hp -EgjAALAcKxHad3A2m67OeYfcgnDmCXRwVWmvo2ifv922ebPynXApVfSr/5Vh88lA -bx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OPYLfykqGxvYmJHzDNw6Yu -YjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+RnlTGNAgMB -AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQW -BBTOw0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPI -QW5pJ6d1Ee88hjZv0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I -0jJmwYrA8y8678Dj1JGG0VDjA9tzd29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4Gni -lmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAWhsI6yLETcDbYz+70CjTVW0z9 -B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0MjomZmWzwPDCv -ON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo -IhNzbM8m9Yop5w== ------END CERTIFICATE----- - -# Issuer: CN=DigiCert Assured ID Root G3 O=DigiCert Inc OU=www.digicert.com -# Subject: CN=DigiCert Assured ID Root G3 O=DigiCert Inc OU=www.digicert.com -# Label: "DigiCert Assured ID Root G3" -# Serial: 15459312981008553731928384953135426796 -# MD5 Fingerprint: 7c:7f:65:31:0c:81:df:8d:ba:3e:99:e2:5c:ad:6e:fb -# SHA1 Fingerprint: f5:17:a2:4f:9a:48:c6:c9:f8:a2:00:26:9f:dc:0f:48:2c:ab:30:89 -# SHA256 Fingerprint: 7e:37:cb:8b:4c:47:09:0c:ab:36:55:1b:a6:f4:5d:b8:40:68:0f:ba:16:6a:95:2d:b1:00:71:7f:43:05:3f:c2 ------BEGIN CERTIFICATE----- -MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw -CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu -ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg -RzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQGEwJV -UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu -Y29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQBgcq -hkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJf -Zn4f5dwbRXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17Q -RSAPWXYQ1qAk8C3eNvJsKTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ -BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgFUaFNN6KDec6NHSrkhDAKBggqhkjOPQQD -AwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5FyYZ5eEJJZVrmDxxDnOOlY -JjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv -6pZjamVFkpUBtA== ------END CERTIFICATE----- - -# Issuer: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com -# Subject: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com -# Label: "DigiCert Global Root G2" -# Serial: 4293743540046975378534879503202253541 -# MD5 Fingerprint: e4:a6:8a:c8:54:ac:52:42:46:0a:fd:72:48:1b:2a:44 -# SHA1 Fingerprint: df:3c:24:f9:bf:d6:66:76:1b:26:80:73:fe:06:d1:cc:8d:4f:82:a4 -# SHA256 Fingerprint: cb:3c:cb:b7:60:31:e5:e0:13:8f:8d:d3:9a:23:f9:de:47:ff:c3:5e:43:c1:14:4c:ea:27:d4:6a:5a:b1:cb:5f ------BEGIN CERTIFICATE----- -MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH -MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT -MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j -b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG -9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI -2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx -1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ -q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz -tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ -vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP -BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV -5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY -1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4 -NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG -Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91 -8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe -pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl -MrY= ------END CERTIFICATE----- - -# Issuer: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com -# Subject: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com -# Label: "DigiCert Global Root G3" -# Serial: 7089244469030293291760083333884364146 -# MD5 Fingerprint: f5:5d:a4:50:a5:fb:28:7e:1e:0f:0d:cc:96:57:56:ca -# SHA1 Fingerprint: 7e:04:de:89:6a:3e:66:6d:00:e6:87:d3:3f:fa:d9:3b:e8:3d:34:9e -# SHA256 Fingerprint: 31:ad:66:48:f8:10:41:38:c7:38:f3:9e:a4:32:01:33:39:3e:3a:18:cc:02:29:6e:f9:7c:2a:c9:ef:67:31:d0 ------BEGIN CERTIFICATE----- -MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw -CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu -ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe -Fw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUw -EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x -IDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0CAQYF -K4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FG -fp4tn+6OYwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPO -Z9wj/wMco+I+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAd -BgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNpYim8S8YwCgYIKoZIzj0EAwMDaAAwZQIx -AK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/ -oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8 -sycX ------END CERTIFICATE----- - -# Issuer: CN=DigiCert Trusted Root G4 O=DigiCert Inc OU=www.digicert.com -# Subject: CN=DigiCert Trusted Root G4 O=DigiCert Inc OU=www.digicert.com -# Label: "DigiCert Trusted Root G4" -# Serial: 7451500558977370777930084869016614236 -# MD5 Fingerprint: 78:f2:fc:aa:60:1f:2f:b4:eb:c9:37:ba:53:2e:75:49 -# SHA1 Fingerprint: dd:fb:16:cd:49:31:c9:73:a2:03:7d:3f:c8:3a:4d:7d:77:5d:05:e4 -# SHA256 Fingerprint: 55:2f:7b:dc:f1:a7:af:9e:6c:e6:72:01:7f:4f:12:ab:f7:72:40:c7:8e:76:1a:c2:03:d1:d9:d2:0a:c8:99:88 ------BEGIN CERTIFICATE----- -MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg -RzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJV -UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu -Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqG -SIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3y -ithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1If -xp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV -ySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiO -DCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQ -jdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/ -CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCi -EhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADM -fRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QY -uKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXK -chYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t -9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB -hjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD -ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2 -SV1EY+CtnJYYZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd -+SeuMIW59mdNOj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWc -fFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqa -sjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9N -cCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N -0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie -4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI -r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1 -/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm -gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+ ------END CERTIFICATE----- - -# Issuer: CN=COMODO RSA Certification Authority O=COMODO CA Limited -# Subject: CN=COMODO RSA Certification Authority O=COMODO CA Limited -# Label: "COMODO RSA Certification Authority" -# Serial: 101909084537582093308941363524873193117 -# MD5 Fingerprint: 1b:31:b0:71:40:36:cc:14:36:91:ad:c4:3e:fd:ec:18 -# SHA1 Fingerprint: af:e5:d2:44:a8:d1:19:42:30:ff:47:9f:e2:f8:97:bb:cd:7a:8c:b4 -# SHA256 Fingerprint: 52:f0:e1:c4:e5:8e:c6:29:29:1b:60:31:7f:07:46:71:b8:5d:7e:a8:0d:5b:07:27:34:63:53:4b:32:b4:02:34 ------BEGIN CERTIFICATE----- -MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB -hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G -A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV -BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5 -MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT -EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR -Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh -dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR -6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X -pz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC -9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV -/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf -Zd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z -+pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w -qP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah -SL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC -u9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf -Fobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq -crxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E -FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB -/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl -wFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM -4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV -2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna -FxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ -CuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK -boHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke -jkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL -S0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb -QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl -0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB -NVOFBkpdn627G190 ------END CERTIFICATE----- - -# Issuer: CN=USERTrust RSA Certification Authority O=The USERTRUST Network -# Subject: CN=USERTrust RSA Certification Authority O=The USERTRUST Network -# Label: "USERTrust RSA Certification Authority" -# Serial: 2645093764781058787591871645665788717 -# MD5 Fingerprint: 1b:fe:69:d1:91:b7:19:33:a3:72:a8:0f:e1:55:e5:b5 -# SHA1 Fingerprint: 2b:8f:1b:57:33:0d:bb:a2:d0:7a:6c:51:f7:0e:e9:0d:da:b9:ad:8e -# SHA256 Fingerprint: e7:93:c9:b0:2f:d8:aa:13:e2:1c:31:22:8a:cc:b0:81:19:64:3b:74:9c:89:89:64:b1:74:6d:46:c3:d4:cb:d2 ------BEGIN CERTIFICATE----- -MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB -iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl -cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV -BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw -MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV -BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU -aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy -dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK -AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B -3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY -tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/ -Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2 -VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT -79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6 -c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT -Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l -c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee -UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE -Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd -BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G -A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF -Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO -VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3 -ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs -8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR -iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze -Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ -XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/ -qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB -VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB -L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG -jjxDah2nGN59PRbxYvnKkKj9 ------END CERTIFICATE----- - -# Issuer: CN=USERTrust ECC Certification Authority O=The USERTRUST Network -# Subject: CN=USERTrust ECC Certification Authority O=The USERTRUST Network -# Label: "USERTrust ECC Certification Authority" -# Serial: 123013823720199481456569720443997572134 -# MD5 Fingerprint: fa:68:bc:d9:b5:7f:ad:fd:c9:1d:06:83:28:cc:24:c1 -# SHA1 Fingerprint: d1:cb:ca:5d:b2:d5:2a:7f:69:3b:67:4d:e5:f0:5a:1d:0c:95:7d:f0 -# SHA256 Fingerprint: 4f:f4:60:d5:4b:9c:86:da:bf:bc:fc:57:12:e0:40:0d:2b:ed:3f:bc:4d:4f:bd:aa:86:e0:6a:dc:d2:a9:ad:7a ------BEGIN CERTIFICATE----- -MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL -MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl -eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT -JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx -MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT -Ck5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg -VVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm -aWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo -I+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng -o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G -A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD -VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB -zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW -RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg= ------END CERTIFICATE----- - -# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R5 -# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R5 -# Label: "GlobalSign ECC Root CA - R5" -# Serial: 32785792099990507226680698011560947931244 -# MD5 Fingerprint: 9f:ad:3b:1c:02:1e:8a:ba:17:74:38:81:0c:a2:bc:08 -# SHA1 Fingerprint: 1f:24:c6:30:cd:a4:18:ef:20:69:ff:ad:4f:dd:5f:46:3a:1b:69:aa -# SHA256 Fingerprint: 17:9f:bc:14:8a:3d:d0:0f:d2:4e:a1:34:58:cc:43:bf:a7:f5:9c:81:82:d7:83:a5:13:f6:eb:ec:10:0c:89:24 ------BEGIN CERTIFICATE----- -MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEk -MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpH -bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX -DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD -QSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu -MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6SFkc -8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8ke -hOvRnkmSh5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD -VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYI -KoZIzj0EAwMDaAAwZQIxAOVpEslu28YxuglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg -515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7yFz9SO8NdCKoCOJuxUnO -xwy8p2Fp8fc74SrL+SvzZpA3 ------END CERTIFICATE----- - -# Issuer: CN=IdenTrust Commercial Root CA 1 O=IdenTrust -# Subject: CN=IdenTrust Commercial Root CA 1 O=IdenTrust -# Label: "IdenTrust Commercial Root CA 1" -# Serial: 13298821034946342390520003877796839426 -# MD5 Fingerprint: b3:3e:77:73:75:ee:a0:d3:e3:7e:49:63:49:59:bb:c7 -# SHA1 Fingerprint: df:71:7e:aa:4a:d9:4e:c9:55:84:99:60:2d:48:de:5f:bc:f0:3a:25 -# SHA256 Fingerprint: 5d:56:49:9b:e4:d2:e0:8b:cf:ca:d0:8a:3e:38:72:3d:50:50:3b:de:70:69:48:e4:2f:55:60:30:19:e5:28:ae ------BEGIN CERTIFICATE----- -MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBK -MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVu -VHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQw -MTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScw -JQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwggIiMA0GCSqG -SIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ldhNlT -3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU -+ehcCuz/mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gp -S0l4PJNgiCL8mdo2yMKi1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1 -bVoE/c40yiTcdCMbXTMTEl3EASX2MN0CXZ/g1Ue9tOsbobtJSdifWwLziuQkkORi -T0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl3ZBWzvurpWCdxJ35UrCL -vYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzyNeVJSQjK -Vsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZK -dHzVWYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHT -c+XvvqDtMwt0viAgxGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hv -l7yTmvmcEpB4eoCHFddydJxVdHixuuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5N -iGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB -/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZIhvcNAQELBQAD -ggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH -6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwt -LRvM7Kqas6pgghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93 -nAbowacYXVKV7cndJZ5t+qntozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3 -+wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmVYjzlVYA211QC//G5Xc7UI2/YRYRK -W2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUXfeu+h1sXIFRRk0pT -AwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/rokTLq -l1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG -4iZZRHUe2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZ -mUlO+KWA2yUPHGNiiskzZ2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A -7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7RcGzM7vRX+Bi6hG6H ------END CERTIFICATE----- - -# Issuer: CN=IdenTrust Public Sector Root CA 1 O=IdenTrust -# Subject: CN=IdenTrust Public Sector Root CA 1 O=IdenTrust -# Label: "IdenTrust Public Sector Root CA 1" -# Serial: 13298821034946342390521976156843933698 -# MD5 Fingerprint: 37:06:a5:b0:fc:89:9d:ba:f4:6b:8c:1a:64:cd:d5:ba -# SHA1 Fingerprint: ba:29:41:60:77:98:3f:f4:f3:ef:f2:31:05:3b:2e:ea:6d:4d:45:fd -# SHA256 Fingerprint: 30:d0:89:5a:9a:44:8a:26:20:91:63:55:22:d1:f5:20:10:b5:86:7a:ca:e1:2c:78:ef:95:8f:d4:f4:38:9f:2f ------BEGIN CERTIFICATE----- -MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBN -MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVu -VHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcN -MzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0 -MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwggIi -MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTyP4o7 -ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGy -RBb06tD6Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlS -bdsHyo+1W/CD80/HLaXIrcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF -/YTLNiCBWS2ab21ISGHKTN9T0a9SvESfqy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R -3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoSmJxZZoY+rfGwyj4GD3vw -EUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFnol57plzy -9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9V -GxyhLrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ -2fjXctscvG29ZV/viDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsV -WaFHVCkugyhfHMKiq3IXAAaOReyL4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gD -W/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ -BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMwDQYJKoZIhvcN -AQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj -t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHV -DRDtfULAj+7AmgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9 -TaDKQGXSc3z1i9kKlT/YPyNtGtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8G -lwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFtm6/n6J91eEyrRjuazr8FGF1NFTwW -mhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMxNRF4eKLg6TCMf4Df -WN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4Mhn5 -+bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJ -tshquDDIajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhA -GaQdp/lLQzfcaFpPz+vCZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv -8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ3Wl9af0AVqW3rLatt8o+Ae+c ------END CERTIFICATE----- - -# Issuer: CN=Entrust Root Certification Authority - G2 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2009 Entrust, Inc. - for authorized use only -# Subject: CN=Entrust Root Certification Authority - G2 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2009 Entrust, Inc. - for authorized use only -# Label: "Entrust Root Certification Authority - G2" -# Serial: 1246989352 -# MD5 Fingerprint: 4b:e2:c9:91:96:65:0c:f4:0e:5a:93:92:a0:0a:fe:b2 -# SHA1 Fingerprint: 8c:f4:27:fd:79:0c:3a:d1:66:06:8d:e8:1e:57:ef:bb:93:22:72:d4 -# SHA256 Fingerprint: 43:df:57:74:b0:3e:7f:ef:5f:e4:0d:93:1a:7b:ed:f1:bb:2e:6b:42:73:8c:4e:6d:38:41:10:3d:3a:a7:f3:39 ------BEGIN CERTIFICATE----- -MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMC -VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50 -cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3Qs -IEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVz -dCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwHhcNMDkwNzA3MTcy -NTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUVu -dHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwt -dGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0 -aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmlj -YXRpb24gQXV0aG9yaXR5IC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK -AoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP/vaCeb9zYQYKpSfYs1/T -RU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXzHHfV1IWN -cCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hW -wcKUs/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1 -U1+cPvQXLOZprE4yTGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0 -jaWvYkxN4FisZDQSA/i2jZRjJKRxAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAP -BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ60B7vfec7aVHUbI2fkBJmqzAN -BgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5ZiXMRrEPR9RP/ -jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ -Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v -1fN2D807iDginWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4R -nAuknZoh8/CbCzB428Hch0P+vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmH -VHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xOe4pIb4tF9g== ------END CERTIFICATE----- - -# Issuer: CN=Entrust Root Certification Authority - EC1 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2012 Entrust, Inc. - for authorized use only -# Subject: CN=Entrust Root Certification Authority - EC1 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2012 Entrust, Inc. - for authorized use only -# Label: "Entrust Root Certification Authority - EC1" -# Serial: 51543124481930649114116133369 -# MD5 Fingerprint: b6:7e:1d:f0:58:c5:49:6c:24:3b:3d:ed:98:18:ed:bc -# SHA1 Fingerprint: 20:d8:06:40:df:9b:25:f5:12:25:3a:11:ea:f7:59:8a:eb:14:b5:47 -# SHA256 Fingerprint: 02:ed:0e:b2:8c:14:da:45:16:5c:56:67:91:70:0d:64:51:d7:fb:56:f0:b2:ab:1d:3b:8e:b0:70:e5:6e:df:f5 ------BEGIN CERTIFICATE----- -MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkG -A1UEBhMCVVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3 -d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVu -dHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEzMDEGA1UEAxMq -RW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRUMxMB4XDTEy -MTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYwFAYD -VQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0 -L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0g -Zm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBD -ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEVDMTB2MBAGByqGSM49AgEGBSuBBAAi -A2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHyAsWfoPZb1YsGGYZPUxBt -ByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef9eNi1KlH -Bz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O -BBYEFLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVC -R98crlOZF7ZvHH3hvxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nX -hTcGtXsI/esni0qU+eH6p44mCOh8kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G ------END CERTIFICATE----- - -# Issuer: CN=CFCA EV ROOT O=China Financial Certification Authority -# Subject: CN=CFCA EV ROOT O=China Financial Certification Authority -# Label: "CFCA EV ROOT" -# Serial: 407555286 -# MD5 Fingerprint: 74:e1:b6:ed:26:7a:7a:44:30:33:94:ab:7b:27:81:30 -# SHA1 Fingerprint: e2:b8:29:4b:55:84:ab:6b:58:c2:90:46:6c:ac:3f:b8:39:8f:84:83 -# SHA256 Fingerprint: 5c:c3:d7:8e:4e:1d:5e:45:54:7a:04:e6:87:3e:64:f9:0c:f9:53:6d:1c:cc:2e:f8:00:f3:55:c4:c5:fd:70:fd ------BEGIN CERTIFICATE----- -MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJD -TjEwMC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9y -aXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkx -MjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEwMC4GA1UECgwnQ2hpbmEgRmluYW5j -aWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJP -T1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnVBU03 -sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpL -TIpTUnrD7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5 -/ZOkVIBMUtRSqy5J35DNuF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp -7hZZLDRJGqgG16iI0gNyejLi6mhNbiyWZXvKWfry4t3uMCz7zEasxGPrb382KzRz -EpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7xzbh72fROdOXW3NiGUgt -hxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9fpy25IGvP -a931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqot -aK8KgWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNg -TnYGmE69g60dWIolhdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfV -PKPtl8MeNPo4+QgO48BdK4PRVmrJtqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hv -cWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAfBgNVHSMEGDAWgBTj/i39KNAL -tbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAd -BgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB -ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObT -ej/tUxPQ4i9qecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdL -jOztUmCypAbqTuv0axn96/Ua4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBS -ESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sGE5uPhnEFtC+NiWYzKXZUmhH4J/qy -P5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfXBDrDMlI1Dlb4pd19 -xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjnaH9d -Ci77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN -5mydLIhyPDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe -/v5WOaHIz16eGWRGENoXkbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+Z -AAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3CekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ -5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su ------END CERTIFICATE----- - -# Issuer: CN=OISTE WISeKey Global Root GB CA O=WISeKey OU=OISTE Foundation Endorsed -# Subject: CN=OISTE WISeKey Global Root GB CA O=WISeKey OU=OISTE Foundation Endorsed -# Label: "OISTE WISeKey Global Root GB CA" -# Serial: 157768595616588414422159278966750757568 -# MD5 Fingerprint: a4:eb:b9:61:28:2e:b7:2f:98:b0:35:26:90:99:51:1d -# SHA1 Fingerprint: 0f:f9:40:76:18:d3:d7:6a:4b:98:f0:a8:35:9e:0c:fd:27:ac:cc:ed -# SHA256 Fingerprint: 6b:9c:08:e8:6e:b0:f7:67:cf:ad:65:cd:98:b6:21:49:e5:49:4a:67:f5:84:5e:7b:d1:ed:01:9f:27:b8:6b:d6 ------BEGIN CERTIFICATE----- -MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBt -MQswCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUg -Rm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9i -YWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAwMzJaFw0zOTEyMDExNTEwMzFaMG0x -CzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBG -b3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh -bCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3 -HEokKtaXscriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGx -WuR51jIjK+FTzJlFXHtPrby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX -1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNk -u7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4oQnc/nSMbsrY9gBQHTC5P -99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvgGUpuuy9r -M2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw -AwEB/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUB -BAMCAQAwDQYJKoZIhvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrgh -cViXfa43FK8+5/ea4n32cZiZBKpDdHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5 -gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0VQreUGdNZtGn//3ZwLWoo4rO -ZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEuiHZeeevJuQHHf -aPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic -Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM= ------END CERTIFICATE----- - -# Issuer: CN=SZAFIR ROOT CA2 O=Krajowa Izba Rozliczeniowa S.A. -# Subject: CN=SZAFIR ROOT CA2 O=Krajowa Izba Rozliczeniowa S.A. -# Label: "SZAFIR ROOT CA2" -# Serial: 357043034767186914217277344587386743377558296292 -# MD5 Fingerprint: 11:64:c1:89:b0:24:b1:8c:b1:07:7e:89:9e:51:9e:99 -# SHA1 Fingerprint: e2:52:fa:95:3f:ed:db:24:60:bd:6e:28:f3:9c:cc:cf:5e:b3:3f:de -# SHA256 Fingerprint: a1:33:9d:33:28:1a:0b:56:e5:57:d3:d3:2b:1c:e7:f9:36:7e:b0:94:bd:5f:a7:2a:7e:50:04:c8:de:d7:ca:fe ------BEGIN CERTIFICATE----- -MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQEL -BQAwUTELMAkGA1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6 -ZW5pb3dhIFMuQS4xGDAWBgNVBAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkw -NzQzMzBaFw0zNTEwMTkwNzQzMzBaMFExCzAJBgNVBAYTAlBMMSgwJgYDVQQKDB9L -cmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRgwFgYDVQQDDA9TWkFGSVIg -Uk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vD5QqEvN -QLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj5/QqGJ3a0a4m7utT -3PSQ1hNKDJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd3BucPbOw -3gAeqDRHu5rr/gsUvTaE2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr6 -3fE9biCloBK0TXC5ztdyO4mTp4CEHCdJckm1/zuVnsHMyAHs6A6KCpbns6aH5db5 -BSsNl0BwPLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwiieDhZNRnvDF5YTy7ykHN -XGoAyDw4jlivAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD -AgEGMB0GA1UdDgQWBBQuFqlKGLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsF -AAOCAQEAtXP4A9xZWx126aMqe5Aosk3AM0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw -8PRBEew/R40/cof5O/2kbytTAOD/OblqBw7rHRz2onKQy4I9EYKL0rufKq8h5mOG -nXkZ7/e7DDWQw4rtTw/1zBLZpD67oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCP -oky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul4+vJhaAlIDf7js4MNIThPIGy -d05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6+/NNIxuZMzSg -LvWpCz/UXeHPhJ/iGcJfitYgHuNztw== ------END CERTIFICATE----- - -# Issuer: CN=Certum Trusted Network CA 2 O=Unizeto Technologies S.A. OU=Certum Certification Authority -# Subject: CN=Certum Trusted Network CA 2 O=Unizeto Technologies S.A. OU=Certum Certification Authority -# Label: "Certum Trusted Network CA 2" -# Serial: 44979900017204383099463764357512596969 -# MD5 Fingerprint: 6d:46:9e:d9:25:6d:08:23:5b:5e:74:7d:1e:27:db:f2 -# SHA1 Fingerprint: d3:dd:48:3e:2b:bf:4c:05:e8:af:10:f5:fa:76:26:cf:d3:dc:30:92 -# SHA256 Fingerprint: b6:76:f2:ed:da:e8:77:5c:d3:6c:b0:f6:3c:d1:d4:60:39:61:f4:9e:62:65:ba:01:3a:2f:03:07:b6:d0:b8:04 ------BEGIN CERTIFICATE----- -MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCB -gDELMAkGA1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMu -QS4xJzAlBgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIG -A1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29yayBDQSAyMCIYDzIwMTExMDA2MDgz -OTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQTDEiMCAGA1UEChMZ -VW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRp -ZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3 -b3JrIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWA -DGSdhhuWZGc/IjoedQF97/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn -0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+oCgCXhVqqndwpyeI1B+twTUrWwbNWuKFB -OJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40bRr5HMNUuctHFY9rnY3lE -fktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2puTRZCr+E -Sv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1m -o130GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02i -sx7QBlrd9pPPV3WZ9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOW -OZV7bIBaTxNyxtd9KXpEulKkKtVBRgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgez -Tv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pyehizKV/Ma5ciSixqClnrDvFAS -adgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vMBhBgu4M1t15n -3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD -AQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMC -AQYwDQYJKoZIhvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQ -F/xlhMcQSZDe28cmk4gmb3DWAl45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTf -CVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuAL55MYIR4PSFk1vtBHxgP58l1cb29 -XN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMoclm2q8KMZiYcdywm -djWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tMpkT/ -WjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jb -AoJnwTnbw3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksq -P/ujmv5zMnHCnsZy4YpoJ/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Ko -b7a6bINDd82Kkhehnlt4Fj1F4jNy3eFmypnTycUm/Q1oBEauttmbjL4ZvrHG8hnj -XALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLXis7VmFxWlgPF7ncGNf/P -5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7zAYspsbi -DrW5viSP ------END CERTIFICATE----- - -# Issuer: CN=Hellenic Academic and Research Institutions RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority -# Subject: CN=Hellenic Academic and Research Institutions RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority -# Label: "Hellenic Academic and Research Institutions RootCA 2015" -# Serial: 0 -# MD5 Fingerprint: ca:ff:e2:db:03:d9:cb:4b:e9:0f:ad:84:fd:7b:18:ce -# SHA1 Fingerprint: 01:0c:06:95:a6:98:19:14:ff:bf:5f:c6:b0:b6:95:ea:29:e9:12:a6 -# SHA256 Fingerprint: a0:40:92:9a:02:ce:53:b4:ac:f4:f2:ff:c6:98:1c:e4:49:6f:75:5e:6d:45:fe:0b:2a:69:2b:cd:52:52:3f:36 ------BEGIN CERTIFICATE----- -MIIGCzCCA/OgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpjELMAkGA1UEBhMCR1Ix -DzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5k -IFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMT -N0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9v -dENBIDIwMTUwHhcNMTUwNzA3MTAxMTIxWhcNNDAwNjMwMTAxMTIxWjCBpjELMAkG -A1UEBhMCR1IxDzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNh -ZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkx -QDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1 -dGlvbnMgUm9vdENBIDIwMTUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC -AQDC+Kk/G4n8PDwEXT2QNrCROnk8ZlrvbTkBSRq0t89/TSNTt5AA4xMqKKYx8ZEA -4yjsriFBzh/a/X0SWwGDD7mwX5nh8hKDgE0GPt+sr+ehiGsxr/CL0BgzuNtFajT0 -AoAkKAoCFZVedioNmToUW/bLy1O8E00BiDeUJRtCvCLYjqOWXjrZMts+6PAQZe10 -4S+nfK8nNLspfZu2zwnI5dMK/IhlZXQK3HMcXM1AsRzUtoSMTFDPaI6oWa7CJ06C -ojXdFPQf/7J31Ycvqm59JCfnxssm5uX+Zwdj2EUN3TpZZTlYepKZcj2chF6IIbjV -9Cz82XBST3i4vTwri5WY9bPRaM8gFH5MXF/ni+X1NYEZN9cRCLdmvtNKzoNXADrD -gfgXy5I2XdGj2HUb4Ysn6npIQf1FGQatJ5lOwXBH3bWfgVMS5bGMSF0xQxfjjMZ6 -Y5ZLKTBOhE5iGV48zpeQpX8B653g+IuJ3SWYPZK2fu/Z8VFRfS0myGlZYeCsargq -NhEEelC9MoS+L9xy1dcdFkfkR2YgP/SWxa+OAXqlD3pk9Q0Yh9muiNX6hME6wGko -LfINaFGq46V3xqSQDqE3izEjR8EJCOtu93ib14L8hCCZSRm2Ekax+0VVFqmjZayc -Bw/qa9wfLgZy7IaIEuQt218FL+TwA9MmM+eAws1CoRc0CwIDAQABo0IwQDAPBgNV -HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUcRVnyMjJvXVd -ctA4GGqd83EkVAswDQYJKoZIhvcNAQELBQADggIBAHW7bVRLqhBYRjTyYtcWNl0I -XtVsyIe9tC5G8jH4fOpCtZMWVdyhDBKg2mF+D1hYc2Ryx+hFjtyp8iY/xnmMsVMI -M4GwVhO+5lFc2JsKT0ucVlMC6U/2DWDqTUJV6HwbISHTGzrMd/K4kPFox/la/vot -9L/J9UUbzjgQKjeKeaO04wlshYaT/4mWJ3iBj2fjRnRUjtkNaeJK9E10A/+yd+2V -Z5fkscWrv2oj6NSU4kQoYsRL4vDY4ilrGnB+JGGTe08DMiUNRSQrlrRGar9KC/ea -j8GsGsVn82800vpzY4zvFrCopEYq+OsS7HK07/grfoxSwIuEVPkvPuNVqNxmsdnh -X9izjFk0WaSrT2y7HxjbdavYy5LNlDhhDgcGH0tGEPEVvo2FXDtKK4F5D7Rpn0lQ -l033DlZdwJVqwjbDG2jJ9SrcR5q+ss7FJej6A7na+RZukYT1HCjI/CbM1xyQVqdf -bzoEvM14iQuODy+jqk+iGxI9FghAD/FGTNeqewjBCvVtJ94Cj8rDtSvK6evIIVM4 -pcw72Hc3MKJP2W/R8kCtQXoXxdZKNYm3QdV8hn9VTYNKpXMgwDqvkPGaJI7ZjnHK -e7iG2rKPmT4dEw0SEe7Uq/DpFXYC5ODfqiAeW2GFZECpkJcNrVPSWh2HagCXZWK0 -vm9qp/UsQu0yrbYhnr68 ------END CERTIFICATE----- - -# Issuer: CN=Hellenic Academic and Research Institutions ECC RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority -# Subject: CN=Hellenic Academic and Research Institutions ECC RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority -# Label: "Hellenic Academic and Research Institutions ECC RootCA 2015" -# Serial: 0 -# MD5 Fingerprint: 81:e5:b4:17:eb:c2:f5:e1:4b:0d:41:7b:49:92:fe:ef -# SHA1 Fingerprint: 9f:f1:71:8d:92:d5:9a:f3:7d:74:97:b4:bc:6f:84:68:0b:ba:b6:66 -# SHA256 Fingerprint: 44:b5:45:aa:8a:25:e6:5a:73:ca:15:dc:27:fc:36:d2:4c:1c:b9:95:3a:06:65:39:b1:15:82:dc:48:7b:48:33 ------BEGIN CERTIFICATE----- -MIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzAN -BgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl -c2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxRDBCBgNVBAMTO0hl -bGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgRUNDIFJv -b3RDQSAyMDE1MB4XDTE1MDcwNzEwMzcxMloXDTQwMDYzMDEwMzcxMlowgaoxCzAJ -BgNVBAYTAkdSMQ8wDQYDVQQHEwZBdGhlbnMxRDBCBgNVBAoTO0hlbGxlbmljIEFj -YWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9yaXR5 -MUQwQgYDVQQDEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0 -dXRpb25zIEVDQyBSb290Q0EgMjAxNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJKg -QehLgoRc4vgxEZmGZE4JJS+dQS8KrjVPdJWyUWRrjWvmP3CV8AVER6ZyOFB2lQJa -jq4onvktTpnvLEhvTCUp6NFxW98dwXU3tNf6e3pCnGoKVlp8aQuqgAkkbH7BRqNC -MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFLQi -C4KZJAEOnLvkDv2/+5cgk5kqMAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaep -lSTAGiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7Sof -TUwJCA3sS61kFyjndc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR ------END CERTIFICATE----- - -# Issuer: CN=ISRG Root X1 O=Internet Security Research Group -# Subject: CN=ISRG Root X1 O=Internet Security Research Group -# Label: "ISRG Root X1" -# Serial: 172886928669790476064670243504169061120 -# MD5 Fingerprint: 0c:d2:f9:e0:da:17:73:e9:ed:86:4d:a5:e3:70:e7:4e -# SHA1 Fingerprint: ca:bd:2a:79:a1:07:6a:31:f2:1d:25:36:35:cb:03:9d:43:29:a5:e8 -# SHA256 Fingerprint: 96:bc:ec:06:26:49:76:f3:74:60:77:9a:cf:28:c5:a7:cf:e8:a3:c0:aa:e1:1a:8f:fc:ee:05:c0:bd:df:08:c6 ------BEGIN CERTIFICATE----- -MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw -TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh -cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 -WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu -ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY -MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc -h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ -0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U -A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW -T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH -B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC -B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv -KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn -OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn -jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw -qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI -rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV -HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq -hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL -ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ -3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK -NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 -ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur -TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC -jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc -oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq -4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA -mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d -emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= ------END CERTIFICATE----- - -# Issuer: O=FNMT-RCM OU=AC RAIZ FNMT-RCM -# Subject: O=FNMT-RCM OU=AC RAIZ FNMT-RCM -# Label: "AC RAIZ FNMT-RCM" -# Serial: 485876308206448804701554682760554759 -# MD5 Fingerprint: e2:09:04:b4:d3:bd:d1:a0:14:fd:1a:d2:47:c4:57:1d -# SHA1 Fingerprint: ec:50:35:07:b2:15:c4:95:62:19:e2:a8:9a:5b:42:99:2c:4c:2c:20 -# SHA256 Fingerprint: eb:c5:57:0c:29:01:8c:4d:67:b1:aa:12:7b:af:12:f7:03:b4:61:1e:bc:17:b7:da:b5:57:38:94:17:9b:93:fa ------BEGIN CERTIFICATE----- -MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsx -CzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJ -WiBGTk1ULVJDTTAeFw0wODEwMjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJ -BgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBG -Tk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALpxgHpMhm5/ -yBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuOi5KOpyVdWRHbNi63URcfqQgf -BBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qsNI1NOHZnjrDIbzAz -WHFctPVrbtQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhfTzC8PhxF -tBDXaEAUwED653cXeuYLj2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z -374jNUUeAlz+taibmSXaXvMiwzn15Cou08YfxGyqxRxqAQVKL9LFwag0Jl1mpdIC -IfkYtwb1TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mwWsXmo8RZZUc1g16p6DUL -mbvkzSDGm0oGObVo/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnTtOmlcYF7 -wk5HlqX2doWjKI/pgG6BU6VtX7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peS -MKGJ47xVqCfWS+2QrYv6YyVZLag13cqXM7zlzced0ezvXg5KkAYmY6252TUtB7p2 -ZSysV4999AeU14ECll2jB0nVetBX+RvnU0Z1qrB5QstocQjpYL05ac70r8NWQMet -UqIJ5G+GR4of6ygnXYMgrwTJbFaai0b1AgMBAAGjgYMwgYAwDwYDVR0TAQH/BAUw -AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFPd9xf3E6Jobd2Sn9R2gzL+H -YJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1odHRwOi8vd3d3 -LmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDD -nFFlm5wioooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1 -RXxlDPiyN8+sD8+Nb/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYM -LVN0V2Ue1bLdI4E7pWYjJ2cJj+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf -77IzlhEYt8llvhjho6Tc+hj507wTmzl6NLrTQfv6MooqtyuGC2mDOL7Nii4LcK2N -JpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71uSANA+iW+YJF1DngoABd15jm -fZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7Ixjp -6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj2zs3gyLp -1txyM/1d8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B -9kiABdcPUXmsEKvU7ANm5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wok -RqEIr9baRRmW1FMdW4R58MD3R++Lj8UGrp1MYp3/RgT408m2ECVAdf4WqslKYIYv -uu8wd+RU4riEmViAqhOLUTpPSPaLtrM= ------END CERTIFICATE----- - -# Issuer: CN=Amazon Root CA 1 O=Amazon -# Subject: CN=Amazon Root CA 1 O=Amazon -# Label: "Amazon Root CA 1" -# Serial: 143266978916655856878034712317230054538369994 -# MD5 Fingerprint: 43:c6:bf:ae:ec:fe:ad:2f:18:c6:88:68:30:fc:c8:e6 -# SHA1 Fingerprint: 8d:a7:f9:65:ec:5e:fc:37:91:0f:1c:6e:59:fd:c1:cc:6a:6e:de:16 -# SHA256 Fingerprint: 8e:cd:e6:88:4f:3d:87:b1:12:5b:a3:1a:c3:fc:b1:3d:70:16:de:7f:57:cc:90:4f:e1:cb:97:c6:ae:98:19:6e ------BEGIN CERTIFICATE----- -MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF -ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 -b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL -MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv -b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj -ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM -9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw -IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6 -VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L -93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm -jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC -AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA -A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI -U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs -N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv -o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU -5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy -rqXRfboQnoZsG4q5WTP468SQvvG5 ------END CERTIFICATE----- - -# Issuer: CN=Amazon Root CA 2 O=Amazon -# Subject: CN=Amazon Root CA 2 O=Amazon -# Label: "Amazon Root CA 2" -# Serial: 143266982885963551818349160658925006970653239 -# MD5 Fingerprint: c8:e5:8d:ce:a8:42:e2:7a:c0:2a:5c:7c:9e:26:bf:66 -# SHA1 Fingerprint: 5a:8c:ef:45:d7:a6:98:59:76:7a:8c:8b:44:96:b5:78:cf:47:4b:1a -# SHA256 Fingerprint: 1b:a5:b2:aa:8c:65:40:1a:82:96:01:18:f8:0b:ec:4f:62:30:4d:83:ce:c4:71:3a:19:c3:9c:01:1e:a4:6d:b4 ------BEGIN CERTIFICATE----- -MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwF -ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 -b24gUm9vdCBDQSAyMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTEL -MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv -b3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK2Wny2cSkxK -gXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4kHbZ -W0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg -1dKmSYXpN+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K -8nu+NQWpEjTj82R0Yiw9AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r -2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvdfLC6HM783k81ds8P+HgfajZRRidhW+me -z/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAExkv8LV/SasrlX6avvDXbR -8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSSbtqDT6Zj -mUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz -7Mt0Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6 -+XUyo05f7O0oYtlNc/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI -0u1ufm8/0i2BWSlmy5A5lREedCf+3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB -Af8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSwDPBMMPQFWAJI/TPlUq9LhONm -UjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oAA7CXDpO8Wqj2 -LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY -+gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kS -k5Nrp+gvU5LEYFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl -7uxMMne0nxrpS10gxdr9HIcWxkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygm -btmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQgj9sAq+uEjonljYE1x2igGOpm/Hl -urR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbWaQbLU8uz/mtBzUF+ -fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoVYh63 -n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE -76KlXIx3KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H -9jVlpNMKVv/1F2Rs76giJUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT -4PsJYGw= ------END CERTIFICATE----- - -# Issuer: CN=Amazon Root CA 3 O=Amazon -# Subject: CN=Amazon Root CA 3 O=Amazon -# Label: "Amazon Root CA 3" -# Serial: 143266986699090766294700635381230934788665930 -# MD5 Fingerprint: a0:d4:ef:0b:f7:b5:d8:49:95:2a:ec:f5:c4:fc:81:87 -# SHA1 Fingerprint: 0d:44:dd:8c:3c:8c:1a:1a:58:75:64:81:e9:0f:2e:2a:ff:b3:d2:6e -# SHA256 Fingerprint: 18:ce:6c:fe:7b:f1:4e:60:b2:e3:47:b8:df:e8:68:cb:31:d0:2e:bb:3a:da:27:15:69:f5:03:43:b4:6d:b3:a4 ------BEGIN CERTIFICATE----- -MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5 -MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g -Um9vdCBDQSAzMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG -A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg -Q0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZBf8ANm+gBG1bG8lKl -ui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjrZt6j -QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSr -ttvXBp43rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkr -BqWTrBqYaGFy+uGh0PsceGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteM -YyRIHN8wfdVoOw== ------END CERTIFICATE----- - -# Issuer: CN=Amazon Root CA 4 O=Amazon -# Subject: CN=Amazon Root CA 4 O=Amazon -# Label: "Amazon Root CA 4" -# Serial: 143266989758080763974105200630763877849284878 -# MD5 Fingerprint: 89:bc:27:d5:eb:17:8d:06:6a:69:d5:fd:89:47:b4:cd -# SHA1 Fingerprint: f6:10:84:07:d6:f8:bb:67:98:0c:c2:e2:44:c2:eb:ae:1c:ef:63:be -# SHA256 Fingerprint: e3:5d:28:41:9e:d0:20:25:cf:a6:90:38:cd:62:39:62:45:8d:a5:c6:95:fb:de:a3:c2:2b:0b:fb:25:89:70:92 ------BEGIN CERTIFICATE----- -MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5 -MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g -Um9vdCBDQSA0MB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG -A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg -Q0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN/sGKe0uoe0ZLY7Bi -9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri83Bk -M6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB -/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WB -MAoGCCqGSM49BAMDA2gAMGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlw -CkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW -1KyLa2tJElMzrdfkviT8tQp21KW8EA== ------END CERTIFICATE----- - -# Issuer: CN=TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 O=Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK OU=Kamu Sertifikasyon Merkezi - Kamu SM -# Subject: CN=TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 O=Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK OU=Kamu Sertifikasyon Merkezi - Kamu SM -# Label: "TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1" -# Serial: 1 -# MD5 Fingerprint: dc:00:81:dc:69:2f:3e:2f:b0:3b:f6:3d:5a:91:8e:49 -# SHA1 Fingerprint: 31:43:64:9b:ec:ce:27:ec:ed:3a:3f:0b:8f:0d:e4:e8:91:dd:ee:ca -# SHA256 Fingerprint: 46:ed:c3:68:90:46:d5:3a:45:3f:b3:10:4a:b8:0d:ca:ec:65:8b:26:60:ea:16:29:dd:7e:86:79:90:64:87:16 ------BEGIN CERTIFICATE----- -MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIx -GDAWBgNVBAcTD0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxp -bXNlbCB2ZSBUZWtub2xvamlrIEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0w -KwYDVQQLEyRLYW11IFNlcnRpZmlrYXN5b24gTWVya2V6aSAtIEthbXUgU00xNjA0 -BgNVBAMTLVRVQklUQUsgS2FtdSBTTSBTU0wgS29rIFNlcnRpZmlrYXNpIC0gU3Vy -dW0gMTAeFw0xMzExMjUwODI1NTVaFw00MzEwMjUwODI1NTVaMIHSMQswCQYDVQQG -EwJUUjEYMBYGA1UEBxMPR2ViemUgLSBLb2NhZWxpMUIwQAYDVQQKEzlUdXJraXll -IEJpbGltc2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAtIFRVQklU -QUsxLTArBgNVBAsTJEthbXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBT -TTE2MDQGA1UEAxMtVFVCSVRBSyBLYW11IFNNIFNTTCBLb2sgU2VydGlmaWthc2kg -LSBTdXJ1bSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr3UwM6q7 -a9OZLBI3hNmNe5eA027n/5tQlT6QlVZC1xl8JoSNkvoBHToP4mQ4t4y86Ij5iySr -LqP1N+RAjhgleYN1Hzv/bKjFxlb4tO2KRKOrbEz8HdDc72i9z+SqzvBV96I01INr -N3wcwv61A+xXzry0tcXtAA9TNypN9E8Mg/uGz8v+jE69h/mniyFXnHrfA2eJLJ2X -YacQuFWQfw4tJzh03+f92k4S400VIgLI4OD8D62K18lUUMw7D8oWgITQUVbDjlZ/ -iSIzL+aFCr2lqBs23tPcLG07xxO9WSMs5uWk99gL7eqQQESolbuT1dCANLZGeA4f -AJNG4e7p+exPFwIDAQABo0IwQDAdBgNVHQ4EFgQUZT/HiobGPN08VFw1+DrtUgxH -V8gwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL -BQADggEBACo/4fEyjq7hmFxLXs9rHmoJ0iKpEsdeV31zVmSAhHqT5Am5EM2fKifh -AHe+SMg1qIGf5LgsyX8OsNJLN13qudULXjS99HMpw+0mFZx+CFOKWI3QSyjfwbPf -IPP54+M638yclNhOT8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4 -lzwDGrpDxpa5RXI4s6ehlj2Re37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c -8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0jq5Rm+K37DwhuJi1/FwcJsoz7UMCf -lo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM= ------END CERTIFICATE----- - -# Issuer: CN=GDCA TrustAUTH R5 ROOT O=GUANG DONG CERTIFICATE AUTHORITY CO.,LTD. -# Subject: CN=GDCA TrustAUTH R5 ROOT O=GUANG DONG CERTIFICATE AUTHORITY CO.,LTD. -# Label: "GDCA TrustAUTH R5 ROOT" -# Serial: 9009899650740120186 -# MD5 Fingerprint: 63:cc:d9:3d:34:35:5c:6f:53:a3:e2:08:70:48:1f:b4 -# SHA1 Fingerprint: 0f:36:38:5b:81:1a:25:c3:9b:31:4e:83:ca:e9:34:66:70:cc:74:b4 -# SHA256 Fingerprint: bf:ff:8f:d0:44:33:48:7d:6a:8a:a6:0c:1a:29:76:7a:9f:c2:bb:b0:5e:42:0f:71:3a:13:b9:92:89:1d:38:93 ------BEGIN CERTIFICATE----- -MIIFiDCCA3CgAwIBAgIIfQmX/vBH6nowDQYJKoZIhvcNAQELBQAwYjELMAkGA1UE -BhMCQ04xMjAwBgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZ -IENPLixMVEQuMR8wHQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMB4XDTE0 -MTEyNjA1MTMxNVoXDTQwMTIzMTE1NTk1OVowYjELMAkGA1UEBhMCQ04xMjAwBgNV -BAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZIENPLixMVEQuMR8w -HQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMIICIjANBgkqhkiG9w0BAQEF -AAOCAg8AMIICCgKCAgEA2aMW8Mh0dHeb7zMNOwZ+Vfy1YI92hhJCfVZmPoiC7XJj -Dp6L3TQsAlFRwxn9WVSEyfFrs0yw6ehGXTjGoqcuEVe6ghWinI9tsJlKCvLriXBj -TnnEt1u9ol2x8kECK62pOqPseQrsXzrj/e+APK00mxqriCZ7VqKChh/rNYmDf1+u -KU49tm7srsHwJ5uu4/Ts765/94Y9cnrrpftZTqfrlYwiOXnhLQiPzLyRuEH3FMEj -qcOtmkVEs7LXLM3GKeJQEK5cy4KOFxg2fZfmiJqwTTQJ9Cy5WmYqsBebnh52nUpm -MUHfP/vFBu8btn4aRjb3ZGM74zkYI+dndRTVdVeSN72+ahsmUPI2JgaQxXABZG12 -ZuGR224HwGGALrIuL4xwp9E7PLOR5G62xDtw8mySlwnNR30YwPO7ng/Wi64HtloP -zgsMR6flPri9fcebNaBhlzpBdRfMK5Z3KpIhHtmVdiBnaM8Nvd/WHwlqmuLMc3Gk -L30SgLdTMEZeS1SZD2fJpcjyIMGC7J0R38IC+xo70e0gmu9lZJIQDSri3nDxGGeC -jGHeuLzRL5z7D9Ar7Rt2ueQ5Vfj4oR24qoAATILnsn8JuLwwoC8N9VKejveSswoA -HQBUlwbgsQfZxw9cZX08bVlX5O2ljelAU58VS6Bx9hoh49pwBiFYFIeFd3mqgnkC -AwEAAaNCMEAwHQYDVR0OBBYEFOLJQJ9NzuiaoXzPDj9lxSmIahlRMA8GA1UdEwEB -/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQDRSVfg -p8xoWLoBDysZzY2wYUWsEe1jUGn4H3++Fo/9nesLqjJHdtJnJO29fDMylyrHBYZm -DRd9FBUb1Ov9H5r2XpdptxolpAqzkT9fNqyL7FeoPueBihhXOYV0GkLH6VsTX4/5 -COmSdI31R9KrO9b7eGZONn356ZLpBN79SWP8bfsUcZNnL0dKt7n/HipzcEYwv1ry -L3ml4Y0M2fmyYzeMN2WFcGpcWwlyua1jPLHd+PwyvzeG5LuOmCd+uh8W4XAR8gPf -JWIyJyYYMoSf/wA6E7qaTfRPuBRwIrHKK5DOKcFw9C+df/KQHtZa37dG/OaG+svg -IHZ6uqbL9XzeYqWxi+7egmaKTjowHz+Ay60nugxe19CxVsp3cbK1daFQqUBDF8Io -2c9Si1vIY9RCPqAzekYu9wogRlR+ak8x8YF+QnQ4ZXMn7sZ8uI7XpTrXmKGcjBBV -09tL7ECQ8s1uV9JiDnxXk7Gnbc2dg7sq5+W2O3FYrf3RRbxake5TFW/TRQl1brqQ -XR4EzzffHqhmsYzmIGrv/EhOdJhCrylvLmrH+33RZjEizIYAfmaDDEL0vTSSwxrq -T8p+ck0LcIymSLumoRT2+1hEmRSuqguTaaApJUqlyyvdimYHFngVV3Eb7PVHhPOe -MTd61X8kreS8/f3MboPoDKi3QWwH3b08hpcv0g== ------END CERTIFICATE----- - -# Issuer: CN=SSL.com Root Certification Authority RSA O=SSL Corporation -# Subject: CN=SSL.com Root Certification Authority RSA O=SSL Corporation -# Label: "SSL.com Root Certification Authority RSA" -# Serial: 8875640296558310041 -# MD5 Fingerprint: 86:69:12:c0:70:f1:ec:ac:ac:c2:d5:bc:a5:5b:a1:29 -# SHA1 Fingerprint: b7:ab:33:08:d1:ea:44:77:ba:14:80:12:5a:6f:bd:a9:36:49:0c:bb -# SHA256 Fingerprint: 85:66:6a:56:2e:e0:be:5c:e9:25:c1:d8:89:0a:6f:76:a8:7e:c1:6d:4d:7d:5f:29:ea:74:19:cf:20:12:3b:69 ------BEGIN CERTIFICATE----- -MIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UE -BhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQK -DA9TU0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZp -Y2F0aW9uIEF1dGhvcml0eSBSU0EwHhcNMTYwMjEyMTczOTM5WhcNNDEwMjEyMTcz -OTM5WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv -dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNv -bSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTCCAiIwDQYJKoZIhvcN -AQEBBQADggIPADCCAgoCggIBAPkP3aMrfcvQKv7sZ4Wm5y4bunfh4/WvpOz6Sl2R -xFdHaxh3a3by/ZPkPQ/CFp4LZsNWlJ4Xg4XOVu/yFv0AYvUiCVToZRdOQbngT0aX -qhvIuG5iXmmxX9sqAn78bMrzQdjt0Oj8P2FI7bADFB0QDksZ4LtO7IZl/zbzXmcC -C52GVWH9ejjt/uIZALdvoVBidXQ8oPrIJZK0bnoix/geoeOy3ZExqysdBP+lSgQ3 -6YWkMyv94tZVNHwZpEpox7Ko07fKoZOI68GXvIz5HdkihCR0xwQ9aqkpk8zruFvh -/l8lqjRYyMEjVJ0bmBHDOJx+PYZspQ9AhnwC9FwCTyjLrnGfDzrIM/4RJTXq/LrF -YD3ZfBjVsqnTdXgDciLKOsMf7yzlLqn6niy2UUb9rwPW6mBo6oUWNmuF6R7As93E -JNyAKoFBbZQ+yODJgUEAnl6/f8UImKIYLEJAs/lvOCdLToD0PYFH4Ih86hzOtXVc -US4cK38acijnALXRdMbX5J+tB5O2UzU1/Dfkw/ZdFr4hc96SCvigY2q8lpJqPvi8 -ZVWb3vUNiSYE/CUapiVpy8JtynziWV+XrOvvLsi81xtZPCvM8hnIk2snYxnP/Okm -+Mpxm3+T/jRnhE6Z6/yzeAkzcLpmpnbtG3PrGqUNxCITIJRWCk4sbE6x/c+cCbqi -M+2HAgMBAAGjYzBhMB0GA1UdDgQWBBTdBAkHovV6fVJTEpKV7jiAJQ2mWTAPBgNV -HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFN0ECQei9Xp9UlMSkpXuOIAlDaZZMA4G -A1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAIBgRlCn7Jp0cHh5wYfGV -cpNxJK1ok1iOMq8bs3AD/CUrdIWQPXhq9LmLpZc7tRiRux6n+UBbkflVma8eEdBc -Hadm47GUBwwyOabqG7B52B2ccETjit3E+ZUfijhDPwGFpUenPUayvOUiaPd7nNgs -PgohyC0zrL/FgZkxdMF1ccW+sfAjRfSda/wZY52jvATGGAslu1OJD7OAUN5F7kR/ -q5R4ZJjT9ijdh9hwZXT7DrkT66cPYakylszeu+1jTBi7qUD3oFRuIIhxdRjqerQ0 -cuAjJ3dctpDqhiVAq+8zD8ufgr6iIPv2tS0a5sKFsXQP+8hlAqRSAUfdSSLBv9jr -a6x+3uxjMxW3IwiPxg+NQVrdjsW5j+VFP3jbutIbQLH+cU0/4IGiul607BXgk90I -H37hVZkLId6Tngr75qNJvTYw/ud3sqB1l7UtgYgXZSD32pAAn8lSzDLKNXz1PQ/Y -K9f1JmzJBjSWFupwWRoyeXkLtoh/D1JIPb9s2KJELtFOt3JY04kTlf5Eq/jXixtu -nLwsoFvVagCvXzfh1foQC5ichucmj87w7G6KVwuA406ywKBjYZC6VWg3dGq2ktuf -oYYitmUnDuy2n0Jg5GfCtdpBC8TTi2EbvPofkSvXRAdeuims2cXp71NIWuuA8ShY -Ic2wBlX7Jz9TkHCpBB5XJ7k= ------END CERTIFICATE----- - -# Issuer: CN=SSL.com Root Certification Authority ECC O=SSL Corporation -# Subject: CN=SSL.com Root Certification Authority ECC O=SSL Corporation -# Label: "SSL.com Root Certification Authority ECC" -# Serial: 8495723813297216424 -# MD5 Fingerprint: 2e:da:e4:39:7f:9c:8f:37:d1:70:9f:26:17:51:3a:8e -# SHA1 Fingerprint: c3:19:7c:39:24:e6:54:af:1b:c4:ab:20:95:7a:e2:c3:0e:13:02:6a -# SHA256 Fingerprint: 34:17:bb:06:cc:60:07:da:1b:96:1c:92:0b:8a:b4:ce:3f:ad:82:0e:4a:a3:0b:9a:cb:c4:a7:4e:bd:ce:bc:65 ------BEGIN CERTIFICATE----- -MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMC -VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T -U0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0 -aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNDAzWhcNNDEwMjEyMTgxNDAz -WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0 -b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNvbSBS -b290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB -BAAiA2IABEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI -7Z4INcgn64mMU1jrYor+8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPg -CemB+vNH06NjMGEwHQYDVR0OBBYEFILRhXMw5zUE044CkvvlpNHEIejNMA8GA1Ud -EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTTjgKS++Wk0cQh6M0wDgYD -VR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCWe+0F+S8T -kdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+ -gA0z5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl ------END CERTIFICATE----- - -# Issuer: CN=SSL.com EV Root Certification Authority RSA R2 O=SSL Corporation -# Subject: CN=SSL.com EV Root Certification Authority RSA R2 O=SSL Corporation -# Label: "SSL.com EV Root Certification Authority RSA R2" -# Serial: 6248227494352943350 -# MD5 Fingerprint: e1:1e:31:58:1a:ae:54:53:02:f6:17:6a:11:7b:4d:95 -# SHA1 Fingerprint: 74:3a:f0:52:9b:d0:32:a0:f4:4a:83:cd:d4:ba:a9:7b:7c:2e:c4:9a -# SHA256 Fingerprint: 2e:7b:f1:6c:c2:24:85:a7:bb:e2:aa:86:96:75:07:61:b0:ae:39:be:3b:2f:e9:d0:cc:6d:4e:f7:34:91:42:5c ------BEGIN CERTIFICATE----- -MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNV -BAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UE -CgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2Vy -dGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIyMB4XDTE3MDUzMTE4MTQzN1oXDTQy -MDUzMDE4MTQzN1owgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4G -A1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQD -DC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIy -MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjzZlQOHWTcDXtOlG2mvq -M0fNTPl9fb69LT3w23jhhqXZuglXaO1XPqDQCEGD5yhBJB/jchXQARr7XnAjssuf -OePPxU7Gkm0mxnu7s9onnQqG6YE3Bf7wcXHswxzpY6IXFJ3vG2fThVUCAtZJycxa -4bH3bzKfydQ7iEGonL3Lq9ttewkfokxykNorCPzPPFTOZw+oz12WGQvE43LrrdF9 -HSfvkusQv1vrO6/PgN3B0pYEW3p+pKk8OHakYo6gOV7qd89dAFmPZiw+B6KjBSYR -aZfqhbcPlgtLyEDhULouisv3D5oi53+aNxPN8k0TayHRwMwi8qFG9kRpnMphNQcA -b9ZhCBHqurj26bNg5U257J8UZslXWNvNh2n4ioYSA0e/ZhN2rHd9NCSFg83XqpyQ -Gp8hLH94t2S42Oim9HizVcuE0jLEeK6jj2HdzghTreyI/BXkmg3mnxp3zkyPuBQV -PWKchjgGAGYS5Fl2WlPAApiiECtoRHuOec4zSnaqW4EWG7WK2NAAe15itAnWhmMO -pgWVSbooi4iTsjQc2KRVbrcc0N6ZVTsj9CLg+SlmJuwgUHfbSguPvuUCYHBBXtSu -UDkiFCbLsjtzdFVHB3mBOagwE0TlBIqulhMlQg+5U8Sb/M3kHN48+qvWBkofZ6aY -MBzdLNvcGJVXZsb/XItW9XcCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNV -HSMEGDAWgBT5YLvU49U09rj1BoAlp3PbRmmonjAdBgNVHQ4EFgQU+WC71OPVNPa4 -9QaAJadz20ZpqJ4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBW -s47LCp1Jjr+kxJG7ZhcFUZh1++VQLHqe8RT6q9OKPv+RKY9ji9i0qVQBDb6Thi/5 -Sm3HXvVX+cpVHBK+Rw82xd9qt9t1wkclf7nxY/hoLVUE0fKNsKTPvDxeH3jnpaAg -cLAExbf3cqfeIg29MyVGjGSSJuM+LmOW2puMPfgYCdcDzH2GguDKBAdRUNf/ktUM -79qGn5nX67evaOI5JpS6aLe/g9Pqemc9YmeuJeVy6OLk7K4S9ksrPJ/psEDzOFSz -/bdoyNrGj1E8svuR3Bznm53htw1yj+KkxKl4+esUrMZDBcJlOSgYAsOCsp0FvmXt -ll9ldDz7CTUue5wT/RsPXcdtgTpWD8w74a8CLyKsRspGPKAcTNZEtF4uXBVmCeEm -Kf7GUmG6sXP/wwyc5WxqlD8UykAWlYTzWamsX0xhk23RO8yilQwipmdnRC652dKK -QbNmC1r7fSOl8hqw/96bg5Qu0T/fkreRrwU7ZcegbLHNYhLDkBvjJc40vG93drEQ -w/cFGsDWr3RiSBd3kmmQYRzelYB0VI8YHMPzA9C/pEN1hlMYegouCRw2n5H9gooi -S9EOUCXdywMMF8mDAAhONU2Ki+3wApRmLER/y5UnlhetCTCstnEXbosX9hwJ1C07 -mKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w== ------END CERTIFICATE----- - -# Issuer: CN=SSL.com EV Root Certification Authority ECC O=SSL Corporation -# Subject: CN=SSL.com EV Root Certification Authority ECC O=SSL Corporation -# Label: "SSL.com EV Root Certification Authority ECC" -# Serial: 3182246526754555285 -# MD5 Fingerprint: 59:53:22:65:83:42:01:54:c0:ce:42:b9:5a:7c:f2:90 -# SHA1 Fingerprint: 4c:dd:51:a3:d1:f5:20:32:14:b0:c6:c5:32:23:03:91:c7:46:42:6d -# SHA256 Fingerprint: 22:a2:c1:f7:bd:ed:70:4c:c1:e7:01:b5:f4:08:c3:10:88:0f:e9:56:b5:de:2a:4a:44:f9:9c:87:3a:25:a7:c8 ------BEGIN CERTIFICATE----- -MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMC -VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T -U0wgQ29ycG9yYXRpb24xNDAyBgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZp -Y2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNTIzWhcNNDEwMjEyMTgx -NTIzWjB/MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv -dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrU1NMLmNv -bSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49 -AgEGBSuBBAAiA2IABKoSR5CYG/vvw0AHgyBO8TCCogbR8pKGYfL2IWjKAMTH6kMA -VIbc/R/fALhBYlzccBYy3h+Z1MzFB8gIH2EWB1E9fVwHU+M1OIzfzZ/ZLg1Kthku -WnBaBu2+8KGwytAJKaNjMGEwHQYDVR0OBBYEFFvKXuXe0oGqzagtZFG22XKbl+ZP -MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe5d7SgarNqC1kUbbZcpuX -5k8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQCK5kCJN+vp1RPZ -ytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZg -h5Mmm7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg== ------END CERTIFICATE----- - -# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R6 -# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R6 -# Label: "GlobalSign Root CA - R6" -# Serial: 1417766617973444989252670301619537 -# MD5 Fingerprint: 4f:dd:07:e4:d4:22:64:39:1e:0c:37:42:ea:d1:c6:ae -# SHA1 Fingerprint: 80:94:64:0e:b5:a7:a1:ca:11:9c:1f:dd:d5:9f:81:02:63:a7:fb:d1 -# SHA256 Fingerprint: 2c:ab:ea:fe:37:d0:6c:a2:2a:ba:73:91:c0:03:3d:25:98:29:52:c4:53:64:73:49:76:3a:3a:b5:ad:6c:cf:69 ------BEGIN CERTIFICATE----- -MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEg -MB4GA1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2Jh -bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQx -MjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSNjET -MBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCAiIwDQYJ -KoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQssgrRI -xutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1k -ZguSgMpE3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxD -aNc9PIrFsmbVkJq3MQbFvuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJw -LnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqMPKq0pPbzlUoSB239jLKJz9CgYXfIWHSw -1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+azayOeSsJDa38O+2HBNX -k7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05OWgtH8wY2 -SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/h -bguyCLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4n -WUx2OVvq+aWh2IMP0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpY -rZxCRXluDocZXFSxZba/jJvcE+kNb7gu3GduyYsRtYQUigAZcIN5kZeR1Bonvzce -MgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTAD -AQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNVHSMEGDAWgBSu -bAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN -nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGt -Ixg93eFyRJa0lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr61 -55wsTLxDKZmOMNOsIeDjHfrYBzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLj -vUYAGm0CuiVdjaExUd1URhxN25mW7xocBFymFe944Hn+Xds+qkxV/ZoVqW/hpvvf -cDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr3TsTjxKM4kEaSHpz -oHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB10jZp -nOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfs -pA9MRf/TuTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+v -JJUEeKgDu+6B5dpffItKoZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R -8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+tJDfLRVpOoERIyNiwmcUVhAn21klJwGW4 -5hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA= ------END CERTIFICATE----- - -# Issuer: CN=OISTE WISeKey Global Root GC CA O=WISeKey OU=OISTE Foundation Endorsed -# Subject: CN=OISTE WISeKey Global Root GC CA O=WISeKey OU=OISTE Foundation Endorsed -# Label: "OISTE WISeKey Global Root GC CA" -# Serial: 44084345621038548146064804565436152554 -# MD5 Fingerprint: a9:d6:b9:2d:2f:93:64:f8:a5:69:ca:91:e9:68:07:23 -# SHA1 Fingerprint: e0:11:84:5e:34:de:be:88:81:b9:9c:f6:16:26:d1:96:1f:c3:b9:31 -# SHA256 Fingerprint: 85:60:f9:1c:36:24:da:ba:95:70:b5:fe:a0:db:e3:6f:f1:1a:83:23:be:94:86:85:4f:b3:f3:4a:55:71:19:8d ------BEGIN CERTIFICATE----- -MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQsw -CQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91 -bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwg -Um9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRaFw00MjA1MDkwOTU4MzNaMG0xCzAJ -BgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBGb3Vu -ZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2JhbCBS -b290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4ni -eUqjFqdrVCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4W -p2OQ0jnUsYd4XxiWD1AbNTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8E -BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7T -rYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0EAwMDaAAwZQIwJsdpW9zV -57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtkAjEA2zQg -Mgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9 ------END CERTIFICATE----- - -# Issuer: CN=UCA Global G2 Root O=UniTrust -# Subject: CN=UCA Global G2 Root O=UniTrust -# Label: "UCA Global G2 Root" -# Serial: 124779693093741543919145257850076631279 -# MD5 Fingerprint: 80:fe:f0:c4:4a:f0:5c:62:32:9f:1c:ba:78:a9:50:f8 -# SHA1 Fingerprint: 28:f9:78:16:19:7a:ff:18:25:18:aa:44:fe:c1:a0:ce:5c:b6:4c:8a -# SHA256 Fingerprint: 9b:ea:11:c9:76:fe:01:47:64:c1:be:56:a6:f9:14:b5:a5:60:31:7a:bd:99:88:39:33:82:e5:16:1a:a0:49:3c ------BEGIN CERTIFICATE----- -MIIFRjCCAy6gAwIBAgIQXd+x2lqj7V2+WmUgZQOQ7zANBgkqhkiG9w0BAQsFADA9 -MQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxGzAZBgNVBAMMElVDQSBH -bG9iYWwgRzIgUm9vdDAeFw0xNjAzMTEwMDAwMDBaFw00MDEyMzEwMDAwMDBaMD0x -CzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlUcnVzdDEbMBkGA1UEAwwSVUNBIEds -b2JhbCBHMiBSb290MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxeYr -b3zvJgUno4Ek2m/LAfmZmqkywiKHYUGRO8vDaBsGxUypK8FnFyIdK+35KYmToni9 -kmugow2ifsqTs6bRjDXVdfkX9s9FxeV67HeToI8jrg4aA3++1NDtLnurRiNb/yzm -VHqUwCoV8MmNsHo7JOHXaOIxPAYzRrZUEaalLyJUKlgNAQLx+hVRZ2zA+te2G3/R -VogvGjqNO7uCEeBHANBSh6v7hn4PJGtAnTRnvI3HLYZveT6OqTwXS3+wmeOwcWDc -C/Vkw85DvG1xudLeJ1uK6NjGruFZfc8oLTW4lVYa8bJYS7cSN8h8s+1LgOGN+jIj -tm+3SJUIsUROhYw6AlQgL9+/V087OpAh18EmNVQg7Mc/R+zvWr9LesGtOxdQXGLY -D0tK3Cv6brxzks3sx1DoQZbXqX5t2Okdj4q1uViSukqSKwxW/YDrCPBeKW4bHAyv -j5OJrdu9o54hyokZ7N+1wxrrFv54NkzWbtA+FxyQF2smuvt6L78RHBgOLXMDj6Dl -NaBa4kx1HXHhOThTeEDMg5PXCp6dW4+K5OXgSORIskfNTip1KnvyIvbJvgmRlld6 -iIis7nCs+dwp4wwcOxJORNanTrAmyPPZGpeRaOrvjUYG0lZFWJo8DA+DuAUlwznP -O6Q0ibd5Ei9Hxeepl2n8pndntd978XplFeRhVmUCAwEAAaNCMEAwDgYDVR0PAQH/ -BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFIHEjMz15DD/pQwIX4wV -ZyF0Ad/fMA0GCSqGSIb3DQEBCwUAA4ICAQATZSL1jiutROTL/7lo5sOASD0Ee/oj -L3rtNtqyzm325p7lX1iPyzcyochltq44PTUbPrw7tgTQvPlJ9Zv3hcU2tsu8+Mg5 -1eRfB70VVJd0ysrtT7q6ZHafgbiERUlMjW+i67HM0cOU2kTC5uLqGOiiHycFutfl -1qnN3e92mI0ADs0b+gO3joBYDic/UvuUospeZcnWhNq5NXHzJsBPd+aBJ9J3O5oU -b3n09tDh05S60FdRvScFDcH9yBIw7m+NESsIndTUv4BFFJqIRNow6rSn4+7vW4LV -PtateJLbXDzz2K36uGt/xDYotgIVilQsnLAXc47QN6MUPJiVAAwpBVueSUmxX8fj -y88nZY41F7dXyDDZQVu5FLbowg+UMaeUmMxq67XhJ/UQqAHojhJi6IjMtX9Gl8Cb -EGY4GjZGXyJoPd/JxhMnq1MGrKI8hgZlb7F+sSlEmqO6SWkoaY/X5V+tBIZkbxqg -DMUIYs6Ao9Dz7GjevjPHF1t/gMRMTLGmhIrDO7gJzRSBuhjjVFc2/tsvfEehOjPI -+Vg7RE+xygKJBJYoaMVLuCaJu9YzL1DV/pqJuhgyklTGW+Cd+V7lDSKb9triyCGy -YiGqhkCyLmTTX8jjfhFnRR8F/uOi77Oos/N9j/gMHyIfLXC0uAE0djAA5SN4p1bX -UB+K+wb1whnw0A== ------END CERTIFICATE----- - -# Issuer: CN=UCA Extended Validation Root O=UniTrust -# Subject: CN=UCA Extended Validation Root O=UniTrust -# Label: "UCA Extended Validation Root" -# Serial: 106100277556486529736699587978573607008 -# MD5 Fingerprint: a1:f3:5f:43:c6:34:9b:da:bf:8c:7e:05:53:ad:96:e2 -# SHA1 Fingerprint: a3:a1:b0:6f:24:61:23:4a:e3:36:a5:c2:37:fc:a6:ff:dd:f0:d7:3a -# SHA256 Fingerprint: d4:3a:f9:b3:54:73:75:5c:96:84:fc:06:d7:d8:cb:70:ee:5c:28:e7:73:fb:29:4e:b4:1e:e7:17:22:92:4d:24 ------BEGIN CERTIFICATE----- -MIIFWjCCA0KgAwIBAgIQT9Irj/VkyDOeTzRYZiNwYDANBgkqhkiG9w0BAQsFADBH -MQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBF -eHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwHhcNMTUwMzEzMDAwMDAwWhcNMzgxMjMx -MDAwMDAwWjBHMQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNV -BAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwggIiMA0GCSqGSIb3DQEB -AQUAA4ICDwAwggIKAoICAQCpCQcoEwKwmeBkqh5DFnpzsZGgdT6o+uM4AHrsiWog -D4vFsJszA1qGxliG1cGFu0/GnEBNyr7uaZa4rYEwmnySBesFK5pI0Lh2PpbIILvS -sPGP2KxFRv+qZ2C0d35qHzwaUnoEPQc8hQ2E0B92CvdqFN9y4zR8V05WAT558aop -O2z6+I9tTcg1367r3CTueUWnhbYFiN6IXSV8l2RnCdm/WhUFhvMJHuxYMjMR83dk -sHYf5BA1FxvyDrFspCqjc/wJHx4yGVMR59mzLC52LqGj3n5qiAno8geK+LLNEOfi -c0CTuwjRP+H8C5SzJe98ptfRr5//lpr1kXuYC3fUfugH0mK1lTnj8/FtDw5lhIpj -VMWAtuCeS31HJqcBCF3RiJ7XwzJE+oJKCmhUfzhTA8ykADNkUVkLo4KRel7sFsLz -KuZi2irbWWIQJUoqgQtHB0MGcIfS+pMRKXpITeuUx3BNr2fVUbGAIAEBtHoIppB/ -TuDvB0GHr2qlXov7z1CymlSvw4m6WC31MJixNnI5fkkE/SmnTHnkBVfblLkWU41G -sx2VYVdWf6/wFlthWG82UBEL2KwrlRYaDh8IzTY0ZRBiZtWAXxQgXy0MoHgKaNYs -1+lvK9JKBZP8nm9rZ/+I8U6laUpSNwXqxhaN0sSZ0YIrO7o1dfdRUVjzyAfd5LQD -fwIDAQABo0IwQDAdBgNVHQ4EFgQU2XQ65DA9DfcS3H5aBZ8eNJr34RQwDwYDVR0T -AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQADggIBADaN -l8xCFWQpN5smLNb7rhVpLGsaGvdftvkHTFnq88nIua7Mui563MD1sC3AO6+fcAUR -ap8lTwEpcOPlDOHqWnzcSbvBHiqB9RZLcpHIojG5qtr8nR/zXUACE/xOHAbKsxSQ -VBcZEhrxH9cMaVr2cXj0lH2RC47skFSOvG+hTKv8dGT9cZr4QQehzZHkPJrgmzI5 -c6sq1WnIeJEmMX3ixzDx/BR4dxIOE/TdFpS/S2d7cFOFyrC78zhNLJA5wA3CXWvp -4uXViI3WLL+rG761KIcSF3Ru/H38j9CHJrAb+7lsq+KePRXBOy5nAliRn+/4Qh8s -t2j1da3Ptfb/EX3C8CSlrdP6oDyp+l3cpaDvRKS+1ujl5BOWF3sGPjLtx7dCvHaj -2GU4Kzg1USEODm8uNBNA4StnDG1KQTAYI1oyVZnJF+A83vbsea0rWBmirSwiGpWO -vpaQXUJXxPkUAzUrHC1RVwinOt4/5Mi0A3PCwSaAuwtCH60NryZy2sy+s6ODWA2C -xR9GUeOcGMyNm43sSet1UNWMKFnKdDTajAshqx7qG+XH/RU+wBeq+yNuJkbL+vmx -cmtpzyKEC2IPrNkZAJSidjzULZrtBJ4tBmIQN1IchXIbJ+XMxjHsN+xjWZsLHXbM -fjKaiJUINlK73nZfdklJrX+9ZSCyycErdhh2n1ax ------END CERTIFICATE----- - -# Issuer: CN=Certigna Root CA O=Dhimyotis OU=0002 48146308100036 -# Subject: CN=Certigna Root CA O=Dhimyotis OU=0002 48146308100036 -# Label: "Certigna Root CA" -# Serial: 269714418870597844693661054334862075617 -# MD5 Fingerprint: 0e:5c:30:62:27:eb:5b:bc:d7:ae:62:ba:e9:d5:df:77 -# SHA1 Fingerprint: 2d:0d:52:14:ff:9e:ad:99:24:01:74:20:47:6e:6c:85:27:27:f5:43 -# SHA256 Fingerprint: d4:8d:3d:23:ee:db:50:a4:59:e5:51:97:60:1c:27:77:4b:9d:7b:18:c9:4d:5a:05:95:11:a1:02:50:b9:31:68 ------BEGIN CERTIFICATE----- -MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAw -WjELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAw -MiA0ODE0NjMwODEwMDAzNjEZMBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0x -MzEwMDEwODMyMjdaFw0zMzEwMDEwODMyMjdaMFoxCzAJBgNVBAYTAkZSMRIwEAYD -VQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYzMDgxMDAwMzYxGTAX -BgNVBAMMEENlcnRpZ25hIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw -ggIKAoICAQDNGDllGlmx6mQWDoyUJJV8g9PFOSbcDO8WV43X2KyjQn+Cyu3NW9sO -ty3tRQgXstmzy9YXUnIo245Onoq2C/mehJpNdt4iKVzSs9IGPjA5qXSjklYcoW9M -CiBtnyN6tMbaLOQdLNyzKNAT8kxOAkmhVECe5uUFoC2EyP+YbNDrihqECB63aCPu -I9Vwzm1RaRDuoXrC0SIxwoKF0vJVdlB8JXrJhFwLrN1CTivngqIkicuQstDuI7pm -TLtipPlTWmR7fJj6o0ieD5Wupxj0auwuA0Wv8HT4Ks16XdG+RCYyKfHx9WzMfgIh -C59vpD++nVPiz32pLHxYGpfhPTc3GGYo0kDFUYqMwy3OU4gkWGQwFsWq4NYKpkDf -ePb1BHxpE4S80dGnBs8B92jAqFe7OmGtBIyT46388NtEbVncSVmurJqZNjBBe3Yz -IoejwpKGbvlw7q6Hh5UbxHq9MfPU0uWZ/75I7HX1eBYdpnDBfzwboZL7z8g81sWT -Co/1VTp2lc5ZmIoJlXcymoO6LAQ6l73UL77XbJuiyn1tJslV1c/DeVIICZkHJC1k -JWumIWmbat10TWuXekG9qxf5kBdIjzb5LdXF2+6qhUVB+s06RbFo5jZMm5BX7CO5 -hwjCxAnxl4YqKE3idMDaxIzb3+KhF1nOJFl0Mdp//TBt2dzhauH8XwIDAQABo4IB -GjCCARYwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE -FBiHVuBud+4kNTxOc5of1uHieX4rMB8GA1UdIwQYMBaAFBiHVuBud+4kNTxOc5of -1uHieX4rMEQGA1UdIAQ9MDswOQYEVR0gADAxMC8GCCsGAQUFBwIBFiNodHRwczov -L3d3d3cuY2VydGlnbmEuZnIvYXV0b3JpdGVzLzBtBgNVHR8EZjBkMC+gLaArhilo -dHRwOi8vY3JsLmNlcnRpZ25hLmZyL2NlcnRpZ25hcm9vdGNhLmNybDAxoC+gLYYr -aHR0cDovL2NybC5kaGlteW90aXMuY29tL2NlcnRpZ25hcm9vdGNhLmNybDANBgkq -hkiG9w0BAQsFAAOCAgEAlLieT/DjlQgi581oQfccVdV8AOItOoldaDgvUSILSo3L -6btdPrtcPbEo/uRTVRPPoZAbAh1fZkYJMyjhDSSXcNMQH+pkV5a7XdrnxIxPTGRG -HVyH41neQtGbqH6mid2PHMkwgu07nM3A6RngatgCdTer9zQoKJHyBApPNeNgJgH6 -0BGM+RFq7q89w1DTj18zeTyGqHNFkIwgtnJzFyO+B2XleJINugHA64wcZr+shncB -lA2c5uk5jR+mUYyZDDl34bSb+hxnV29qao6pK0xXeXpXIs/NX2NGjVxZOob4Mkdi -o2cNGJHc+6Zr9UhhcyNZjgKnvETq9Emd8VRY+WCv2hikLyhF3HqgiIZd8zvn/yk1 -gPxkQ5Tm4xxvvq0OKmOZK8l+hfZx6AYDlf7ej0gcWtSS6Cvu5zHbugRqh5jnxV/v -faci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaYtlu3zM63 -Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayh -jWZSaX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw -3kAP+HwV96LOPNdeE4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0= ------END CERTIFICATE----- - -# Issuer: CN=emSign Root CA - G1 O=eMudhra Technologies Limited OU=emSign PKI -# Subject: CN=emSign Root CA - G1 O=eMudhra Technologies Limited OU=emSign PKI -# Label: "emSign Root CA - G1" -# Serial: 235931866688319308814040 -# MD5 Fingerprint: 9c:42:84:57:dd:cb:0b:a7:2e:95:ad:b6:f3:da:bc:ac -# SHA1 Fingerprint: 8a:c7:ad:8f:73:ac:4e:c1:b5:75:4d:a5:40:f4:fc:cf:7c:b5:8e:8c -# SHA256 Fingerprint: 40:f6:af:03:46:a9:9a:a1:cd:1d:55:5a:4e:9c:ce:62:c7:f9:63:46:03:ee:40:66:15:83:3d:c8:c8:d0:03:67 ------BEGIN CERTIFICATE----- -MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYD -VQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBU -ZWNobm9sb2dpZXMgTGltaXRlZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBH -MTAeFw0xODAyMTgxODMwMDBaFw00MzAyMTgxODMwMDBaMGcxCzAJBgNVBAYTAklO -MRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVkaHJhIFRlY2hub2xv -Z2llcyBMaW1pdGVkMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEcxMIIBIjAN -BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0u76WaK7p1b1TST0Bsew+eeuGQz -f2N4aLTNLnF115sgxk0pvLZoYIr3IZpWNVrzdr3YzZr/k1ZLpVkGoZM0Kd0WNHVO -8oG0x5ZOrRkVUkr+PHB1cM2vK6sVmjM8qrOLqs1D/fXqcP/tzxE7lM5OMhbTI0Aq -d7OvPAEsbO2ZLIvZTmmYsvePQbAyeGHWDV/D+qJAkh1cF+ZwPjXnorfCYuKrpDhM -tTk1b+oDafo6VGiFbdbyL0NVHpENDtjVaqSW0RM8LHhQ6DqS0hdW5TUaQBw+jSzt -Od9C4INBdN+jzcKGYEho42kLVACL5HZpIQ15TjQIXhTCzLG3rdd8cIrHhQIDAQAB -o0IwQDAdBgNVHQ4EFgQU++8Nhp6w492pufEhF38+/PB3KxowDgYDVR0PAQH/BAQD -AgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFn/8oz1h31x -PaOfG1vR2vjTnGs2vZupYeveFix0PZ7mddrXuqe8QhfnPZHr5X3dPpzxz5KsbEjM -wiI/aTvFthUvozXGaCocV685743QNcMYDHsAVhzNixl03r4PEuDQqqE/AjSxcM6d -GNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q+Mri/Tm3R7nrft8EI6/6nAYH -6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeihU80Bv2noWgby -RQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx -iN66zB+Afko= ------END CERTIFICATE----- - -# Issuer: CN=emSign ECC Root CA - G3 O=eMudhra Technologies Limited OU=emSign PKI -# Subject: CN=emSign ECC Root CA - G3 O=eMudhra Technologies Limited OU=emSign PKI -# Label: "emSign ECC Root CA - G3" -# Serial: 287880440101571086945156 -# MD5 Fingerprint: ce:0b:72:d1:9f:88:8e:d0:50:03:e8:e3:b8:8b:67:40 -# SHA1 Fingerprint: 30:43:fa:4f:f2:57:dc:a0:c3:80:ee:2e:58:ea:78:b2:3f:e6:bb:c1 -# SHA256 Fingerprint: 86:a1:ec:ba:08:9c:4a:8d:3b:be:27:34:c6:12:ba:34:1d:81:3e:04:3c:f9:e8:a8:62:cd:5c:57:a3:6b:be:6b ------BEGIN CERTIFICATE----- -MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQG -EwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNo -bm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g -RzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4MTgzMDAwWjBrMQswCQYDVQQGEwJJ -TjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9s -b2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMw -djAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0 -WXTsuwYc58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xyS -fvalY8L1X44uT6EYGQIrMgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuB -zhccLikenEhjQjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggq -hkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+DCBeQyh+KTOgNG3qxrdWB -CUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7jHvrZQnD -+JbNR6iC8hZVdyR+EhCVBCyj ------END CERTIFICATE----- - -# Issuer: CN=emSign Root CA - C1 O=eMudhra Inc OU=emSign PKI -# Subject: CN=emSign Root CA - C1 O=eMudhra Inc OU=emSign PKI -# Label: "emSign Root CA - C1" -# Serial: 825510296613316004955058 -# MD5 Fingerprint: d8:e3:5d:01:21:fa:78:5a:b0:df:ba:d2:ee:2a:5f:68 -# SHA1 Fingerprint: e7:2e:f1:df:fc:b2:09:28:cf:5d:d4:d5:67:37:b1:51:cb:86:4f:01 -# SHA256 Fingerprint: 12:56:09:aa:30:1d:a0:a2:49:b9:7a:82:39:cb:6a:34:21:6f:44:dc:ac:9f:39:54:b1:42:92:f2:e8:c8:60:8f ------BEGIN CERTIFICATE----- -MIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkG -A1UEBhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEg -SW5jMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEMxMB4XDTE4MDIxODE4MzAw -MFoXDTQzMDIxODE4MzAwMFowVjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln -biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQDExNlbVNpZ24gUm9v -dCBDQSAtIEMxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+upufGZ -BczYKCFK83M0UYRWEPWgTywS4/oTmifQz/l5GnRfHXk5/Fv4cI7gklL35CX5VIPZ -HdPIWoU/Xse2B+4+wM6ar6xWQio5JXDWv7V7Nq2s9nPczdcdioOl+yuQFTdrHCZH -3DspVpNqs8FqOp099cGXOFgFixwR4+S0uF2FHYP+eF8LRWgYSKVGczQ7/g/IdrvH -GPMF0Ybzhe3nudkyrVWIzqa2kbBPrH4VI5b2P/AgNBbeCsbEBEV5f6f9vtKppa+c -xSMq9zwhbL2vj07FOrLzNBL834AaSaTUqZX3noleoomslMuoaJuvimUnzYnu3Yy1 -aylwQ6BpC+S5DwIDAQABo0IwQDAdBgNVHQ4EFgQU/qHgcB4qAzlSWkK+XJGFehiq -TbUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL -BQADggEBAMJKVvoVIXsoounlHfv4LcQ5lkFMOycsxGwYFYDGrK9HWS8mC+M2sO87 -/kOXSTKZEhVb3xEp/6tT+LvBeA+snFOvV71ojD1pM/CjoCNjO2RnIkSt1XHLVip4 -kqNPEjE2NuLe/gDEo2APJ62gsIq1NnpSob0n9CAnYuhNlCQT5AoE6TyrLshDCUrG -YQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9wC68AivTxEDkigcxHpvOJpkT -+xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQBmIMMMAVSKeo -WXzhriKi4gp6D/piq1JM4fHfyr6DDUI= ------END CERTIFICATE----- - -# Issuer: CN=emSign ECC Root CA - C3 O=eMudhra Inc OU=emSign PKI -# Subject: CN=emSign ECC Root CA - C3 O=eMudhra Inc OU=emSign PKI -# Label: "emSign ECC Root CA - C3" -# Serial: 582948710642506000014504 -# MD5 Fingerprint: 3e:53:b3:a3:81:ee:d7:10:f8:d3:b0:1d:17:92:f5:d5 -# SHA1 Fingerprint: b6:af:43:c2:9b:81:53:7d:f6:ef:6b:c3:1f:1f:60:15:0c:ee:48:66 -# SHA256 Fingerprint: bc:4d:80:9b:15:18:9d:78:db:3e:1d:8c:f4:f9:72:6a:79:5d:a1:64:3c:a5:f1:35:8e:1d:db:0e:dc:0d:7e:b3 ------BEGIN CERTIFICATE----- -MIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQG -EwJVUzETMBEGA1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMx -IDAeBgNVBAMTF2VtU2lnbiBFQ0MgUm9vdCBDQSAtIEMzMB4XDTE4MDIxODE4MzAw -MFoXDTQzMDIxODE4MzAwMFowWjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln -biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMSAwHgYDVQQDExdlbVNpZ24gRUND -IFJvb3QgQ0EgLSBDMzB2MBAGByqGSM49AgEGBSuBBAAiA2IABP2lYa57JhAd6bci -MK4G9IGzsUJxlTm801Ljr6/58pc1kjZGDoeVjbk5Wum739D+yAdBPLtVb4Ojavti -sIGJAnB9SMVK4+kiVCJNk7tCDK93nCOmfddhEc5lx/h//vXyqaNCMEAwHQYDVR0O -BBYEFPtaSNCAIEDyqOkAB2kZd6fmw/TPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB -Af8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQC02C8Cif22TGK6Q04ThHK1rt0c -3ta13FaPWEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwUZOR8loMRnLDRWmFLpg9J -0wD8ofzkpf9/rdcw0Md3f76BB1UwUCAU9Vc4CqgxUQ== ------END CERTIFICATE----- - -# Issuer: CN=Hongkong Post Root CA 3 O=Hongkong Post -# Subject: CN=Hongkong Post Root CA 3 O=Hongkong Post -# Label: "Hongkong Post Root CA 3" -# Serial: 46170865288971385588281144162979347873371282084 -# MD5 Fingerprint: 11:fc:9f:bd:73:30:02:8a:fd:3f:f3:58:b9:cb:20:f0 -# SHA1 Fingerprint: 58:a2:d0:ec:20:52:81:5b:c1:f3:f8:64:02:24:4e:c2:8e:02:4b:02 -# SHA256 Fingerprint: 5a:2f:c0:3f:0c:83:b0:90:bb:fa:40:60:4b:09:88:44:6c:76:36:18:3d:f9:84:6e:17:10:1a:44:7f:b8:ef:d6 ------BEGIN CERTIFICATE----- -MIIFzzCCA7egAwIBAgIUCBZfikyl7ADJk0DfxMauI7gcWqQwDQYJKoZIhvcNAQEL -BQAwbzELMAkGA1UEBhMCSEsxEjAQBgNVBAgTCUhvbmcgS29uZzESMBAGA1UEBxMJ -SG9uZyBLb25nMRYwFAYDVQQKEw1Ib25na29uZyBQb3N0MSAwHgYDVQQDExdIb25n -a29uZyBQb3N0IFJvb3QgQ0EgMzAeFw0xNzA2MDMwMjI5NDZaFw00MjA2MDMwMjI5 -NDZaMG8xCzAJBgNVBAYTAkhLMRIwEAYDVQQIEwlIb25nIEtvbmcxEjAQBgNVBAcT -CUhvbmcgS29uZzEWMBQGA1UEChMNSG9uZ2tvbmcgUG9zdDEgMB4GA1UEAxMXSG9u -Z2tvbmcgUG9zdCBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK -AoICAQCziNfqzg8gTr7m1gNt7ln8wlffKWihgw4+aMdoWJwcYEuJQwy51BWy7sFO -dem1p+/l6TWZ5Mwc50tfjTMwIDNT2aa71T4Tjukfh0mtUC1Qyhi+AViiE3CWu4mI -VoBc+L0sPOFMV4i707mV78vH9toxdCim5lSJ9UExyuUmGs2C4HDaOym71QP1mbpV -9WTRYA6ziUm4ii8F0oRFKHyPaFASePwLtVPLwpgchKOesL4jpNrcyCse2m5FHomY -2vkALgbpDDtw1VAliJnLzXNg99X/NWfFobxeq81KuEXryGgeDQ0URhLj0mRiikKY -vLTGCAj4/ahMZJx2Ab0vqWwzD9g/KLg8aQFChn5pwckGyuV6RmXpwtZQQS4/t+Tt -bNe/JgERohYpSms0BpDsE9K2+2p20jzt8NYt3eEV7KObLyzJPivkaTv/ciWxNoZb -x39ri1UbSsUgYT2uy1DhCDq+sI9jQVMwCFk8mB13umOResoQUGC/8Ne8lYePl8X+ -l2oBlKN8W4UdKjk60FSh0Tlxnf0h+bV78OLgAo9uliQlLKAeLKjEiafv7ZkGL7YK -TE/bosw3Gq9HhS2KX8Q0NEwA/RiTZxPRN+ZItIsGxVd7GYYKecsAyVKvQv83j+Gj -Hno9UKtjBucVtT+2RTeUN7F+8kjDf8V1/peNRY8apxpyKBpADwIDAQABo2MwYTAP -BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQXnc0e -i9Y5K3DTXNSguB+wAPzFYTAdBgNVHQ4EFgQUF53NHovWOStw01zUoLgfsAD8xWEw -DQYJKoZIhvcNAQELBQADggIBAFbVe27mIgHSQpsY1Q7XZiNc4/6gx5LS6ZStS6LG -7BJ8dNVI0lkUmcDrudHr9EgwW62nV3OZqdPlt9EuWSRY3GguLmLYauRwCy0gUCCk -MpXRAJi70/33MvJJrsZ64Ee+bs7Lo3I6LWldy8joRTnU+kLBEUx3XZL7av9YROXr -gZ6voJmtvqkBZss4HTzfQx/0TW60uhdG/H39h4F5ag0zD/ov+BS5gLNdTaqX4fnk -GMX41TiMJjz98iji7lpJiCzfeT2OnpA8vUFKOt1b9pq0zj8lMH8yfaIDlNDceqFS -3m6TjRgm/VWsvY+b0s+v54Ysyx8Jb6NvqYTUc79NoXQbTiNg8swOqn+knEwlqLJm -Ozj/2ZQw9nKEvmhVEA/GcywWaZMH/rFF7buiVWqw2rVKAiUnhde3t4ZEFolsgCs+ -l6mc1X5VTMbeRRAc6uk7nwNT7u56AQIWeNTowr5GdogTPyK7SBIdUgC0An4hGh6c -JfTzPV4e0hz5sy229zdcxsshTrD3mUcYhcErulWuBurQB7Lcq9CClnXO0lD+mefP -L5/ndtFhKvshuzHQqp9HpLIiyhY6UFfEW0NnxWViA0kB60PZ2Pierc+xYw5F9KBa -LJstxabArahH9CdMOA0uG0k7UvToiIMrVCjU8jVStDKDYmlkDJGcn5fqdBb9HxEG -mpv0 ------END CERTIFICATE----- - -# Issuer: CN=Microsoft ECC Root Certificate Authority 2017 O=Microsoft Corporation -# Subject: CN=Microsoft ECC Root Certificate Authority 2017 O=Microsoft Corporation -# Label: "Microsoft ECC Root Certificate Authority 2017" -# Serial: 136839042543790627607696632466672567020 -# MD5 Fingerprint: dd:a1:03:e6:4a:93:10:d1:bf:f0:19:42:cb:fe:ed:67 -# SHA1 Fingerprint: 99:9a:64:c3:7f:f4:7d:9f:ab:95:f1:47:69:89:14:60:ee:c4:c3:c5 -# SHA256 Fingerprint: 35:8d:f3:9d:76:4a:f9:e1:b7:66:e9:c9:72:df:35:2e:e1:5c:fa:c2:27:af:6a:d1:d7:0e:8e:4a:6e:dc:ba:02 ------BEGIN CERTIFICATE----- -MIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQsw -CQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYD -VQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIw -MTcwHhcNMTkxMjE4MjMwNjQ1WhcNNDIwNzE4MjMxNjA0WjBlMQswCQYDVQQGEwJV -UzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNy -b3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwdjAQBgcq -hkjOPQIBBgUrgQQAIgNiAATUvD0CQnVBEyPNgASGAlEvaqiBYgtlzPbKnR5vSmZR -ogPZnZH6thaxjG7efM3beaYvzrvOcS/lpaso7GMEZpn4+vKTEAXhgShC48Zo9OYb -hGBKia/teQ87zvH2RPUBeMCjVDBSMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8E -BTADAQH/MB0GA1UdDgQWBBTIy5lycFIM+Oa+sgRXKSrPQhDtNTAQBgkrBgEEAYI3 -FQEEAwIBADAKBggqhkjOPQQDAwNoADBlAjBY8k3qDPlfXu5gKcs68tvWMoQZP3zV -L8KxzJOuULsJMsbG7X7JNpQS5GiFBqIb0C8CMQCZ6Ra0DvpWSNSkMBaReNtUjGUB -iudQZsIxtzm6uBoiB078a1QWIP8rtedMDE2mT3M= ------END CERTIFICATE----- - -# Issuer: CN=Microsoft RSA Root Certificate Authority 2017 O=Microsoft Corporation -# Subject: CN=Microsoft RSA Root Certificate Authority 2017 O=Microsoft Corporation -# Label: "Microsoft RSA Root Certificate Authority 2017" -# Serial: 40975477897264996090493496164228220339 -# MD5 Fingerprint: 10:ff:00:ff:cf:c9:f8:c7:7a:c0:ee:35:8e:c9:0f:47 -# SHA1 Fingerprint: 73:a5:e6:4a:3b:ff:83:16:ff:0e:dc:cc:61:8a:90:6e:4e:ae:4d:74 -# SHA256 Fingerprint: c7:41:f7:0f:4b:2a:8d:88:bf:2e:71:c1:41:22:ef:53:ef:10:eb:a0:cf:a5:e6:4c:fa:20:f4:18:85:30:73:e0 ------BEGIN CERTIFICATE----- -MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBl -MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw -NAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 -IDIwMTcwHhcNMTkxMjE4MjI1MTIyWhcNNDIwNzE4MjMwMDIzWjBlMQswCQYDVQQG -EwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1N -aWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwggIi -MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKW76UM4wplZEWCpW9R2LBifOZ -Nt9GkMml7Xhqb0eRaPgnZ1AzHaGm++DlQ6OEAlcBXZxIQIJTELy/xztokLaCLeX0 -ZdDMbRnMlfl7rEqUrQ7eS0MdhweSE5CAg2Q1OQT85elss7YfUJQ4ZVBcF0a5toW1 -HLUX6NZFndiyJrDKxHBKrmCk3bPZ7Pw71VdyvD/IybLeS2v4I2wDwAW9lcfNcztm -gGTjGqwu+UcF8ga2m3P1eDNbx6H7JyqhtJqRjJHTOoI+dkC0zVJhUXAoP8XFWvLJ -jEm7FFtNyP9nTUwSlq31/niol4fX/V4ggNyhSyL71Imtus5Hl0dVe49FyGcohJUc -aDDv70ngNXtk55iwlNpNhTs+VcQor1fznhPbRiefHqJeRIOkpcrVE7NLP8TjwuaG -YaRSMLl6IE9vDzhTyzMMEyuP1pq9KsgtsRx9S1HKR9FIJ3Jdh+vVReZIZZ2vUpC6 -W6IYZVcSn2i51BVrlMRpIpj0M+Dt+VGOQVDJNE92kKz8OMHY4Xu54+OU4UZpyw4K -UGsTuqwPN1q3ErWQgR5WrlcihtnJ0tHXUeOrO8ZV/R4O03QK0dqq6mm4lyiPSMQH -+FJDOvTKVTUssKZqwJz58oHhEmrARdlns87/I6KJClTUFLkqqNfs+avNJVgyeY+Q -W5g5xAgGwax/Dj0ApQIDAQABo1QwUjAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/ -BAUwAwEB/zAdBgNVHQ4EFgQUCctZf4aycI8awznjwNnpv7tNsiMwEAYJKwYBBAGC -NxUBBAMCAQAwDQYJKoZIhvcNAQEMBQADggIBAKyvPl3CEZaJjqPnktaXFbgToqZC -LgLNFgVZJ8og6Lq46BrsTaiXVq5lQ7GPAJtSzVXNUzltYkyLDVt8LkS/gxCP81OC -gMNPOsduET/m4xaRhPtthH80dK2Jp86519efhGSSvpWhrQlTM93uCupKUY5vVau6 -tZRGrox/2KJQJWVggEbbMwSubLWYdFQl3JPk+ONVFT24bcMKpBLBaYVu32TxU5nh -SnUgnZUP5NbcA/FZGOhHibJXWpS2qdgXKxdJ5XbLwVaZOjex/2kskZGT4d9Mozd2 -TaGf+G0eHdP67Pv0RR0Tbc/3WeUiJ3IrhvNXuzDtJE3cfVa7o7P4NHmJweDyAmH3 -pvwPuxwXC65B2Xy9J6P9LjrRk5Sxcx0ki69bIImtt2dmefU6xqaWM/5TkshGsRGR -xpl/j8nWZjEgQRCHLQzWwa80mMpkg/sTV9HB8Dx6jKXB/ZUhoHHBk2dxEuqPiApp -GWSZI1b7rCoucL5mxAyE7+WL85MB+GqQk2dLsmijtWKP6T+MejteD+eMuMZ87zf9 -dOLITzNy4ZQ5bb0Sr74MTnB8G2+NszKTc0QWbej09+CVgI+WXTik9KveCjCHk9hN -AHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D5KbvtwEwXlGjefVwaaZB -RA+GsCyRxj3qrg+E ------END CERTIFICATE----- - -# Issuer: CN=e-Szigno Root CA 2017 O=Microsec Ltd. -# Subject: CN=e-Szigno Root CA 2017 O=Microsec Ltd. -# Label: "e-Szigno Root CA 2017" -# Serial: 411379200276854331539784714 -# MD5 Fingerprint: de:1f:f6:9e:84:ae:a7:b4:21:ce:1e:58:7d:d1:84:98 -# SHA1 Fingerprint: 89:d4:83:03:4f:9e:9a:48:80:5f:72:37:d4:a9:a6:ef:cb:7c:1f:d1 -# SHA256 Fingerprint: be:b0:0b:30:83:9b:9b:c3:2c:32:e4:44:79:05:95:06:41:f2:64:21:b1:5e:d0:89:19:8b:51:8a:e2:ea:1b:99 ------BEGIN CERTIFICATE----- -MIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNV -BAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRk -LjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJv -b3QgQ0EgMjAxNzAeFw0xNzA4MjIxMjA3MDZaFw00MjA4MjIxMjA3MDZaMHExCzAJ -BgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMg -THRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25v -IFJvb3QgQ0EgMjAxNzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJbcPYrYsHtv -xie+RJCxs1YVe45DJH0ahFnuY2iyxl6H0BVIHqiQrb1TotreOpCmYF9oMrWGQd+H -Wyx7xf58etqjYzBhMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G -A1UdDgQWBBSHERUI0arBeAyxr87GyZDvvzAEwDAfBgNVHSMEGDAWgBSHERUI0arB -eAyxr87GyZDvvzAEwDAKBggqhkjOPQQDAgNJADBGAiEAtVfd14pVCzbhhkT61Nlo -jbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxOsvxyqltZ -+efcMQ== ------END CERTIFICATE----- - -# Issuer: O=CERTSIGN SA OU=certSIGN ROOT CA G2 -# Subject: O=CERTSIGN SA OU=certSIGN ROOT CA G2 -# Label: "certSIGN Root CA G2" -# Serial: 313609486401300475190 -# MD5 Fingerprint: 8c:f1:75:8a:c6:19:cf:94:b7:f7:65:20:87:c3:97:c7 -# SHA1 Fingerprint: 26:f9:93:b4:ed:3d:28:27:b0:b9:4b:a7:e9:15:1d:a3:8d:92:e5:32 -# SHA256 Fingerprint: 65:7c:fe:2f:a7:3f:aa:38:46:25:71:f3:32:a2:36:3a:46:fc:e7:02:09:51:71:07:02:cd:fb:b6:ee:da:33:05 ------BEGIN CERTIFICATE----- -MIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNV -BAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04g -Uk9PVCBDQSBHMjAeFw0xNzAyMDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJ -BgNVBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJ -R04gUk9PVCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMDF -dRmRfUR0dIf+DjuW3NgBFszuY5HnC2/OOwppGnzC46+CjobXXo9X69MhWf05N0Iw -vlDqtg+piNguLWkh59E3GE59kdUWX2tbAMI5Qw02hVK5U2UPHULlj88F0+7cDBrZ -uIt4ImfkabBoxTzkbFpG583H+u/E7Eu9aqSs/cwoUe+StCmrqzWaTOTECMYmzPhp -n+Sc8CnTXPnGFiWeI8MgwT0PPzhAsP6CRDiqWhqKa2NYOLQV07YRaXseVO6MGiKs -cpc/I1mbySKEwQdPzH/iV8oScLumZfNpdWO9lfsbl83kqK/20U6o2YpxJM02PbyW -xPFsqa7lzw1uKA2wDrXKUXt4FMMgL3/7FFXhEZn91QqhngLjYl/rNUssuHLoPj1P -rCy7Lobio3aP5ZMqz6WryFyNSwb/EkaseMsUBzXgqd+L6a8VTxaJW732jcZZroiF -DsGJ6x9nxUWO/203Nit4ZoORUSs9/1F3dmKh7Gc+PoGD4FapUB8fepmrY7+EF3fx -DTvf95xhszWYijqy7DwaNz9+j5LP2RIUZNoQAhVB/0/E6xyjyfqZ90bp4RjZsbgy -LcsUDFDYg2WD7rlcz8sFWkz6GZdr1l0T08JcVLwyc6B49fFtHsufpaafItzRUZ6C -eWRgKRM+o/1Pcmqr4tTluCRVLERLiohEnMqE0yo7AgMBAAGjQjBAMA8GA1UdEwEB -/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSCIS1mxteg4BXrzkwJ -d8RgnlRuAzANBgkqhkiG9w0BAQsFAAOCAgEAYN4auOfyYILVAzOBywaK8SJJ6ejq -kX/GM15oGQOGO0MBzwdw5AgeZYWR5hEit/UCI46uuR59H35s5r0l1ZUa8gWmr4UC -b6741jH/JclKyMeKqdmfS0mbEVeZkkMR3rYzpMzXjWR91M08KCy0mpbqTfXERMQl -qiCA2ClV9+BB/AYm/7k29UMUA2Z44RGx2iBfRgB4ACGlHgAoYXhvqAEBj500mv/0 -OJD7uNGzcgbJceaBxXntC6Z58hMLnPddDnskk7RI24Zf3lCGeOdA5jGokHZwYa+c -NywRtYK3qq4kNFtyDGkNzVmf9nGvnAvRCjj5BiKDUyUM/FHE5r7iOZULJK2v0ZXk -ltd0ZGtxTgI8qoXzIKNDOXZbbFD+mpwUHmUUihW9o4JFWklWatKcsWMy5WHgUyIO -pwpJ6st+H6jiYoD2EEVSmAYY3qXNL3+q1Ok+CHLsIwMCPKaq2LxndD0UF/tUSxfj -03k9bWtJySgOLnRQvwzZRjoQhsmnP+mg7H/rpXdYaXHmgwo38oZJar55CJD2AhZk -PuXaTH4MNMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE -1LlSVHJ7liXMvGnjSG4N0MedJ5qq+BOS3R7fY581qRY27Iy4g/Q9iY/NtBde17MX -QRBdJ3NghVdJIgc= ------END CERTIFICATE----- - -# Issuer: CN=Trustwave Global Certification Authority O=Trustwave Holdings, Inc. -# Subject: CN=Trustwave Global Certification Authority O=Trustwave Holdings, Inc. -# Label: "Trustwave Global Certification Authority" -# Serial: 1846098327275375458322922162 -# MD5 Fingerprint: f8:1c:18:2d:2f:ba:5f:6d:a1:6c:bc:c7:ab:91:c7:0e -# SHA1 Fingerprint: 2f:8f:36:4f:e1:58:97:44:21:59:87:a5:2a:9a:d0:69:95:26:7f:b5 -# SHA256 Fingerprint: 97:55:20:15:f5:dd:fc:3c:87:88:c0:06:94:45:55:40:88:94:45:00:84:f1:00:86:70:86:bc:1a:2b:b5:8d:c8 ------BEGIN CERTIFICATE----- -MIIF2jCCA8KgAwIBAgIMBfcOhtpJ80Y1LrqyMA0GCSqGSIb3DQEBCwUAMIGIMQsw -CQYDVQQGEwJVUzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28x -ITAfBgNVBAoMGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1 -c3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMx -OTM0MTJaFw00MjA4MjMxOTM0MTJaMIGIMQswCQYDVQQGEwJVUzERMA8GA1UECAwI -SWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2ZSBI -b2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZp -Y2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB -ALldUShLPDeS0YLOvR29zd24q88KPuFd5dyqCblXAj7mY2Hf8g+CY66j96xz0Xzn -swuvCAAJWX/NKSqIk4cXGIDtiLK0thAfLdZfVaITXdHG6wZWiYj+rDKd/VzDBcdu -7oaJuogDnXIhhpCujwOl3J+IKMujkkkP7NAP4m1ET4BqstTnoApTAbqOl5F2brz8 -1Ws25kCI1nsvXwXoLG0R8+eyvpJETNKXpP7ScoFDB5zpET71ixpZfR9oWN0EACyW -80OzfpgZdNmcc9kYvkHHNHnZ9GLCQ7mzJ7Aiy/k9UscwR7PJPrhq4ufogXBeQotP -JqX+OsIgbrv4Fo7NDKm0G2x2EOFYeUY+VM6AqFcJNykbmROPDMjWLBz7BegIlT1l -RtzuzWniTY+HKE40Cz7PFNm73bZQmq131BnW2hqIyE4bJ3XYsgjxroMwuREOzYfw -hI0Vcnyh78zyiGG69Gm7DIwLdVcEuE4qFC49DxweMqZiNu5m4iK4BUBjECLzMx10 -coos9TkpoNPnG4CELcU9402x/RpvumUHO1jsQkUm+9jaJXLE9gCxInm943xZYkqc -BW89zubWR2OZxiRvchLIrH+QtAuRcOi35hYQcRfO3gZPSEF9NUqjifLJS3tBEW1n -twiYTOURGa5CgNz7kAXU+FDKvuStx8KU1xad5hePrzb7AgMBAAGjQjBAMA8GA1Ud -EwEB/wQFMAMBAf8wHQYDVR0OBBYEFJngGWcNYtt2s9o9uFvo/ULSMQ6HMA4GA1Ud -DwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAmHNw4rDT7TnsTGDZqRKGFx6W -0OhUKDtkLSGm+J1WE2pIPU/HPinbbViDVD2HfSMF1OQc3Og4ZYbFdada2zUFvXfe -uyk3QAUHw5RSn8pk3fEbK9xGChACMf1KaA0HZJDmHvUqoai7PF35owgLEQzxPy0Q -lG/+4jSHg9bP5Rs1bdID4bANqKCqRieCNqcVtgimQlRXtpla4gt5kNdXElE1GYhB -aCXUNxeEFfsBctyV3lImIJgm4nb1J2/6ADtKYdkNy1GTKv0WBpanI5ojSP5RvbbE -sLFUzt5sQa0WZ37b/TjNuThOssFgy50X31ieemKyJo90lZvkWx3SD92YHJtZuSPT -MaCm/zjdzyBP6VhWOmfD0faZmZ26NraAL4hHT4a/RDqA5Dccprrql5gR0IRiR2Qe -qu5AvzSxnI9O4fKSTx+O856X3vOmeWqJcU9LJxdI/uz0UA9PSX3MReO9ekDFQdxh -VicGaeVyQYHTtgGJoC86cnn+OjC/QezHYj6RS8fZMXZC+fc8Y+wmjHMMfRod6qh8 -h6jCJ3zhM0EPz8/8AKAigJ5Kp28AsEFFtyLKaEjFQqKu3R3y4G5OBVixwJAWKqQ9 -EEC+j2Jjg6mcgn0tAumDMHzLJ8n9HmYAsC7TIS+OMxZsmO0QqAfWzJPP29FpHOTK -yeC2nOnOcXHebD8WpHk= ------END CERTIFICATE----- - -# Issuer: CN=Trustwave Global ECC P256 Certification Authority O=Trustwave Holdings, Inc. -# Subject: CN=Trustwave Global ECC P256 Certification Authority O=Trustwave Holdings, Inc. -# Label: "Trustwave Global ECC P256 Certification Authority" -# Serial: 4151900041497450638097112925 -# MD5 Fingerprint: 5b:44:e3:8d:5d:36:86:26:e8:0d:05:d2:59:a7:83:54 -# SHA1 Fingerprint: b4:90:82:dd:45:0c:be:8b:5b:b1:66:d3:e2:a4:08:26:cd:ed:42:cf -# SHA256 Fingerprint: 94:5b:bc:82:5e:a5:54:f4:89:d1:fd:51:a7:3d:df:2e:a6:24:ac:70:19:a0:52:05:22:5c:22:a7:8c:cf:a8:b4 ------BEGIN CERTIFICATE----- -MIICYDCCAgegAwIBAgIMDWpfCD8oXD5Rld9dMAoGCCqGSM49BAMCMIGRMQswCQYD -VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAf -BgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3 -YXZlIEdsb2JhbCBFQ0MgUDI1NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0x -NzA4MjMxOTM1MTBaFw00MjA4MjMxOTM1MTBaMIGRMQswCQYDVQQGEwJVUzERMA8G -A1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0 -d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBF -Q0MgUDI1NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqG -SM49AwEHA0IABH77bOYj43MyCMpg5lOcunSNGLB4kFKA3TjASh3RqMyTpJcGOMoN -FWLGjgEqZZ2q3zSRLoHB5DOSMcT9CTqmP62jQzBBMA8GA1UdEwEB/wQFMAMBAf8w -DwYDVR0PAQH/BAUDAwcGADAdBgNVHQ4EFgQUo0EGrJBt0UrrdaVKEJmzsaGLSvcw -CgYIKoZIzj0EAwIDRwAwRAIgB+ZU2g6gWrKuEZ+Hxbb/ad4lvvigtwjzRM4q3wgh -DDcCIC0mA6AFvWvR9lz4ZcyGbbOcNEhjhAnFjXca4syc4XR7 ------END CERTIFICATE----- - -# Issuer: CN=Trustwave Global ECC P384 Certification Authority O=Trustwave Holdings, Inc. -# Subject: CN=Trustwave Global ECC P384 Certification Authority O=Trustwave Holdings, Inc. -# Label: "Trustwave Global ECC P384 Certification Authority" -# Serial: 2704997926503831671788816187 -# MD5 Fingerprint: ea:cf:60:c4:3b:b9:15:29:40:a1:97:ed:78:27:93:d6 -# SHA1 Fingerprint: e7:f3:a3:c8:cf:6f:c3:04:2e:6d:0e:67:32:c5:9e:68:95:0d:5e:d2 -# SHA256 Fingerprint: 55:90:38:59:c8:c0:c3:eb:b8:75:9e:ce:4e:25:57:22:5f:f5:75:8b:bd:38:eb:d4:82:76:60:1e:1b:d5:80:97 ------BEGIN CERTIFICATE----- -MIICnTCCAiSgAwIBAgIMCL2Fl2yZJ6SAaEc7MAoGCCqGSM49BAMDMIGRMQswCQYD -VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAf -BgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3 -YXZlIEdsb2JhbCBFQ0MgUDM4NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0x -NzA4MjMxOTM2NDNaFw00MjA4MjMxOTM2NDNaMIGRMQswCQYDVQQGEwJVUzERMA8G -A1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0 -d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBF -Q0MgUDM4NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTB2MBAGByqGSM49AgEGBSuB -BAAiA2IABGvaDXU1CDFHBa5FmVXxERMuSvgQMSOjfoPTfygIOiYaOs+Xgh+AtycJ -j9GOMMQKmw6sWASr9zZ9lCOkmwqKi6vr/TklZvFe/oyujUF5nQlgziip04pt89ZF -1PKYhDhloKNDMEEwDwYDVR0TAQH/BAUwAwEB/zAPBgNVHQ8BAf8EBQMDBwYAMB0G -A1UdDgQWBBRVqYSJ0sEyvRjLbKYHTsjnnb6CkDAKBggqhkjOPQQDAwNnADBkAjA3 -AZKXRRJ+oPM+rRk6ct30UJMDEr5E0k9BpIycnR+j9sKS50gU/k6bpZFXrsY3crsC -MGclCrEMXu6pY5Jv5ZAL/mYiykf9ijH3g/56vxC+GCsej/YpHpRZ744hN8tRmKVu -Sw== ------END CERTIFICATE----- - -# Issuer: CN=NAVER Global Root Certification Authority O=NAVER BUSINESS PLATFORM Corp. -# Subject: CN=NAVER Global Root Certification Authority O=NAVER BUSINESS PLATFORM Corp. -# Label: "NAVER Global Root Certification Authority" -# Serial: 9013692873798656336226253319739695165984492813 -# MD5 Fingerprint: c8:7e:41:f6:25:3b:f5:09:b3:17:e8:46:3d:bf:d0:9b -# SHA1 Fingerprint: 8f:6b:f2:a9:27:4a:da:14:a0:c4:f4:8e:61:27:f9:c0:1e:78:5d:d1 -# SHA256 Fingerprint: 88:f4:38:dc:f8:ff:d1:fa:8f:42:91:15:ff:e5:f8:2a:e1:e0:6e:0c:70:c3:75:fa:ad:71:7b:34:a4:9e:72:65 ------BEGIN CERTIFICATE----- -MIIFojCCA4qgAwIBAgIUAZQwHqIL3fXFMyqxQ0Rx+NZQTQ0wDQYJKoZIhvcNAQEM -BQAwaTELMAkGA1UEBhMCS1IxJjAkBgNVBAoMHU5BVkVSIEJVU0lORVNTIFBMQVRG -T1JNIENvcnAuMTIwMAYDVQQDDClOQVZFUiBHbG9iYWwgUm9vdCBDZXJ0aWZpY2F0 -aW9uIEF1dGhvcml0eTAeFw0xNzA4MTgwODU4NDJaFw0zNzA4MTgyMzU5NTlaMGkx -CzAJBgNVBAYTAktSMSYwJAYDVQQKDB1OQVZFUiBCVVNJTkVTUyBQTEFURk9STSBD -b3JwLjEyMDAGA1UEAwwpTkFWRVIgR2xvYmFsIFJvb3QgQ2VydGlmaWNhdGlvbiBB -dXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC21PGTXLVA -iQqrDZBbUGOukJR0F0Vy1ntlWilLp1agS7gvQnXp2XskWjFlqxcX0TM62RHcQDaH -38dq6SZeWYp34+hInDEW+j6RscrJo+KfziFTowI2MMtSAuXaMl3Dxeb57hHHi8lE -HoSTGEq0n+USZGnQJoViAbbJAh2+g1G7XNr4rRVqmfeSVPc0W+m/6imBEtRTkZaz -kVrd/pBzKPswRrXKCAfHcXLJZtM0l/aM9BhK4dA9WkW2aacp+yPOiNgSnABIqKYP -szuSjXEOdMWLyEz59JuOuDxp7W87UC9Y7cSw0BwbagzivESq2M0UXZR4Yb8Obtoq -vC8MC3GmsxY/nOb5zJ9TNeIDoKAYv7vxvvTWjIcNQvcGufFt7QSUqP620wbGQGHf -nZ3zVHbOUzoBppJB7ASjjw2i1QnK1sua8e9DXcCrpUHPXFNwcMmIpi3Ua2FzUCaG -YQ5fG8Ir4ozVu53BA0K6lNpfqbDKzE0K70dpAy8i+/Eozr9dUGWokG2zdLAIx6yo -0es+nPxdGoMuK8u180SdOqcXYZaicdNwlhVNt0xz7hlcxVs+Qf6sdWA7G2POAN3a -CJBitOUt7kinaxeZVL6HSuOpXgRM6xBtVNbv8ejyYhbLgGvtPe31HzClrkvJE+2K -AQHJuFFYwGY6sWZLxNUxAmLpdIQM201GLQIDAQABo0IwQDAdBgNVHQ4EFgQU0p+I -36HNLL3s9TsBAZMzJ7LrYEswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB -Af8wDQYJKoZIhvcNAQEMBQADggIBADLKgLOdPVQG3dLSLvCkASELZ0jKbY7gyKoN -qo0hV4/GPnrK21HUUrPUloSlWGB/5QuOH/XcChWB5Tu2tyIvCZwTFrFsDDUIbatj -cu3cvuzHV+YwIHHW1xDBE1UBjCpD5EHxzzp6U5LOogMFDTjfArsQLtk70pt6wKGm -+LUx5vR1yblTmXVHIloUFcd4G7ad6Qz4G3bxhYTeodoS76TiEJd6eN4MUZeoIUCL -hr0N8F5OSza7OyAfikJW4Qsav3vQIkMsRIz75Sq0bBwcupTgE34h5prCy8VCZLQe -lHsIJchxzIdFV4XTnyliIoNRlwAYl3dqmJLJfGBs32x9SuRwTMKeuB330DTHD8z7 -p/8Dvq1wkNoL3chtl1+afwkyQf3NosxabUzyqkn+Zvjp2DXrDige7kgvOtB5CTh8 -piKCk5XQA76+AqAF3SAi428diDRgxuYKuQl1C/AH6GmWNcf7I4GOODm4RStDeKLR -LBT/DShycpWbXgnbiUSYqqFJu3FS8r/2/yehNq+4tneI3TqkbZs0kNwUXTC/t+sX -5Ie3cdCh13cV1ELX8vMxmV2b3RZtP+oGI/hGoiLtk/bdmuYqh7GYVPEi92tF4+KO -dh2ajcQGjTa3FPOdVGm3jjzVpG2Tgbet9r1ke8LJaDmgkpzNNIaRkPpkUZ3+/uul -9XXeifdy ------END CERTIFICATE----- - -# Issuer: CN=AC RAIZ FNMT-RCM SERVIDORES SEGUROS O=FNMT-RCM OU=Ceres -# Subject: CN=AC RAIZ FNMT-RCM SERVIDORES SEGUROS O=FNMT-RCM OU=Ceres -# Label: "AC RAIZ FNMT-RCM SERVIDORES SEGUROS" -# Serial: 131542671362353147877283741781055151509 -# MD5 Fingerprint: 19:36:9c:52:03:2f:d2:d1:bb:23:cc:dd:1e:12:55:bb -# SHA1 Fingerprint: 62:ff:d9:9e:c0:65:0d:03:ce:75:93:d2:ed:3f:2d:32:c9:e3:e5:4a -# SHA256 Fingerprint: 55:41:53:b1:3d:2c:f9:dd:b7:53:bf:be:1a:4e:0a:e0:8d:0a:a4:18:70:58:fe:60:a2:b8:62:b2:e4:b8:7b:cb ------BEGIN CERTIFICATE----- -MIICbjCCAfOgAwIBAgIQYvYybOXE42hcG2LdnC6dlTAKBggqhkjOPQQDAzB4MQsw -CQYDVQQGEwJFUzERMA8GA1UECgwIRk5NVC1SQ00xDjAMBgNVBAsMBUNlcmVzMRgw -FgYDVQRhDA9WQVRFUy1RMjgyNjAwNEoxLDAqBgNVBAMMI0FDIFJBSVogRk5NVC1S -Q00gU0VSVklET1JFUyBTRUdVUk9TMB4XDTE4MTIyMDA5MzczM1oXDTQzMTIyMDA5 -MzczM1oweDELMAkGA1UEBhMCRVMxETAPBgNVBAoMCEZOTVQtUkNNMQ4wDAYDVQQL -DAVDZXJlczEYMBYGA1UEYQwPVkFURVMtUTI4MjYwMDRKMSwwKgYDVQQDDCNBQyBS -QUlaIEZOTVQtUkNNIFNFUlZJRE9SRVMgU0VHVVJPUzB2MBAGByqGSM49AgEGBSuB -BAAiA2IABPa6V1PIyqvfNkpSIeSX0oNnnvBlUdBeh8dHsVnyV0ebAAKTRBdp20LH -sbI6GA60XYyzZl2hNPk2LEnb80b8s0RpRBNm/dfF/a82Tc4DTQdxz69qBdKiQ1oK -Um8BA06Oi6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD -VR0OBBYEFAG5L++/EYZg8k/QQW6rcx/n0m5JMAoGCCqGSM49BAMDA2kAMGYCMQCu -SuMrQMN0EfKVrRYj3k4MGuZdpSRea0R7/DjiT8ucRRcRTBQnJlU5dUoDzBOQn5IC -MQD6SmxgiHPz7riYYqnOK8LZiqZwMR2vsJRM60/G49HzYqc8/5MuB1xJAWdpEgJy -v+c= ------END CERTIFICATE----- - -# Issuer: CN=GlobalSign Root R46 O=GlobalSign nv-sa -# Subject: CN=GlobalSign Root R46 O=GlobalSign nv-sa -# Label: "GlobalSign Root R46" -# Serial: 1552617688466950547958867513931858518042577 -# MD5 Fingerprint: c4:14:30:e4:fa:66:43:94:2a:6a:1b:24:5f:19:d0:ef -# SHA1 Fingerprint: 53:a2:b0:4b:ca:6b:d6:45:e6:39:8a:8e:c4:0d:d2:bf:77:c3:a2:90 -# SHA256 Fingerprint: 4f:a3:12:6d:8d:3a:11:d1:c4:85:5a:4f:80:7c:ba:d6:cf:91:9d:3a:5a:88:b0:3b:ea:2c:63:72:d9:3c:40:c9 ------BEGIN CERTIFICATE----- -MIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAUA -MEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYD -VQQDExNHbG9iYWxTaWduIFJvb3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMy -MDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYt -c2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEB -AQUAA4ICDwAwggIKAoICAQCsrHQy6LNl5brtQyYdpokNRbopiLKkHWPd08EsCVeJ -OaFV6Wc0dwxu5FUdUiXSE2te4R2pt32JMl8Nnp8semNgQB+msLZ4j5lUlghYruQG -vGIFAha/r6gjA7aUD7xubMLL1aa7DOn2wQL7Id5m3RerdELv8HQvJfTqa1VbkNud -316HCkD7rRlr+/fKYIje2sGP1q7Vf9Q8g+7XFkyDRTNrJ9CG0Bwta/OrffGFqfUo -0q3v84RLHIf8E6M6cqJaESvWJ3En7YEtbWaBkoe0G1h6zD8K+kZPTXhc+CtI4wSE -y132tGqzZfxCnlEmIyDLPRT5ge1lFgBPGmSXZgjPjHvjK8Cd+RTyG/FWaha/LIWF -zXg4mutCagI0GIMXTpRW+LaCtfOW3T3zvn8gdz57GSNrLNRyc0NXfeD412lPFzYE -+cCQYDdF3uYM2HSNrpyibXRdQr4G9dlkbgIQrImwTDsHTUB+JMWKmIJ5jqSngiCN -I/onccnfxkF0oE32kRbcRoxfKWMxWXEM2G/CtjJ9++ZdU6Z+Ffy7dXxd7Pj2Fxzs -x2sZy/N78CsHpdlseVR2bJ0cpm4O6XkMqCNqo98bMDGfsVR7/mrLZqrcZdCinkqa -ByFrgY/bxFn63iLABJzjqls2k+g9vXqhnQt2sQvHnf3PmKgGwvgqo6GDoLclcqUC -4wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV -HQ4EFgQUA1yrc4GHqMywptWU4jaWSf8FmSwwDQYJKoZIhvcNAQEMBQADggIBAHx4 -7PYCLLtbfpIrXTncvtgdokIzTfnvpCo7RGkerNlFo048p9gkUbJUHJNOxO97k4Vg -JuoJSOD1u8fpaNK7ajFxzHmuEajwmf3lH7wvqMxX63bEIaZHU1VNaL8FpO7XJqti -2kM3S+LGteWygxk6x9PbTZ4IevPuzz5i+6zoYMzRx6Fcg0XERczzF2sUyQQCPtIk -pnnpHs6i58FZFZ8d4kuaPp92CC1r2LpXFNqD6v6MVenQTqnMdzGxRBF6XLE+0xRF -FRhiJBPSy03OXIPBNvIQtQ6IbbjhVp+J3pZmOUdkLG5NrmJ7v2B0GbhWrJKsFjLt -rWhV/pi60zTe9Mlhww6G9kuEYO4Ne7UyWHmRVSyBQ7N0H3qqJZ4d16GLuc1CLgSk -ZoNNiTW2bKg2SnkheCLQQrzRQDGQob4Ez8pn7fXwgNNgyYMqIgXQBztSvwyeqiv5 -u+YfjyW6hY0XHgL+XVAEV8/+LbzvXMAaq7afJMbfc2hIkCwU9D9SGuTSyxTDYWnP -4vkYxboznxSjBF25cfe1lNj2M8FawTSLfJvdkzrnE6JwYZ+vj+vYxXX4M2bUdGc6 -N3ec592kD3ZDZopD8p/7DEJ4Y9HiD2971KE9dJeFt0g5QdYg/NA6s/rob8SKunE3 -vouXsXgxT7PntgMTzlSdriVZzH81Xwj3QEUxeCp6 ------END CERTIFICATE----- - -# Issuer: CN=GlobalSign Root E46 O=GlobalSign nv-sa -# Subject: CN=GlobalSign Root E46 O=GlobalSign nv-sa -# Label: "GlobalSign Root E46" -# Serial: 1552617690338932563915843282459653771421763 -# MD5 Fingerprint: b5:b8:66:ed:de:08:83:e3:c9:e2:01:34:06:ac:51:6f -# SHA1 Fingerprint: 39:b4:6c:d5:fe:80:06:eb:e2:2f:4a:bb:08:33:a0:af:db:b9:dd:84 -# SHA256 Fingerprint: cb:b9:c4:4d:84:b8:04:3e:10:50:ea:31:a6:9f:51:49:55:d7:bf:d2:e2:c6:b4:93:01:01:9a:d6:1d:9f:50:58 ------BEGIN CERTIFICATE----- -MIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDMEYx -CzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQD -ExNHbG9iYWxTaWduIFJvb3QgRTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAw -MDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2Ex -HDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUrgQQA -IgNiAAScDrHPt+ieUnd1NPqlRqetMhkytAepJ8qUuwzSChDH2omwlwxwEwkBjtjq -R+q+soArzfwoDdusvKSGN+1wCAB16pMLey5SnCNoIwZD7JIvU4Tb+0cUB+hflGdd -yXqBPCCjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud -DgQWBBQxCpCPtsad0kRLgLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ -7Zvvi5QCkxeCmb6zniz2C5GMn0oUsfZkvLtoURMMA/cVi4RguYv/Uo7njLwcAjA8 -+RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+CAezNIm8BZ/3Hobui3A= ------END CERTIFICATE----- - -# Issuer: CN=GLOBALTRUST 2020 O=e-commerce monitoring GmbH -# Subject: CN=GLOBALTRUST 2020 O=e-commerce monitoring GmbH -# Label: "GLOBALTRUST 2020" -# Serial: 109160994242082918454945253 -# MD5 Fingerprint: 8a:c7:6f:cb:6d:e3:cc:a2:f1:7c:83:fa:0e:78:d7:e8 -# SHA1 Fingerprint: d0:67:c1:13:51:01:0c:aa:d0:c7:6a:65:37:31:16:26:4f:53:71:a2 -# SHA256 Fingerprint: 9a:29:6a:51:82:d1:d4:51:a2:e3:7f:43:9b:74:da:af:a2:67:52:33:29:f9:0f:9a:0d:20:07:c3:34:e2:3c:9a ------BEGIN CERTIFICATE----- -MIIFgjCCA2qgAwIBAgILWku9WvtPilv6ZeUwDQYJKoZIhvcNAQELBQAwTTELMAkG -A1UEBhMCQVQxIzAhBgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkw -FwYDVQQDExBHTE9CQUxUUlVTVCAyMDIwMB4XDTIwMDIxMDAwMDAwMFoXDTQwMDYx -MDAwMDAwMFowTTELMAkGA1UEBhMCQVQxIzAhBgNVBAoTGmUtY29tbWVyY2UgbW9u -aXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVTVCAyMDIwMIICIjANBgkq -hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAri5WrRsc7/aVj6B3GyvTY4+ETUWiD59b -RatZe1E0+eyLinjF3WuvvcTfk0Uev5E4C64OFudBc/jbu9G4UeDLgztzOG53ig9Z -YybNpyrOVPu44sB8R85gfD+yc/LAGbaKkoc1DZAoouQVBGM+uq/ufF7MpotQsjj3 -QWPKzv9pj2gOlTblzLmMCcpL3TGQlsjMH/1WljTbjhzqLL6FLmPdqqmV0/0plRPw -yJiT2S0WR5ARg6I6IqIoV6Lr/sCMKKCmfecqQjuCgGOlYx8ZzHyyZqjC0203b+J+ -BlHZRYQfEs4kUmSFC0iAToexIiIwquuuvuAC4EDosEKAA1GqtH6qRNdDYfOiaxaJ -SaSjpCuKAsR49GiKweR6NrFvG5Ybd0mN1MkGco/PU+PcF4UgStyYJ9ORJitHHmkH -r96i5OTUawuzXnzUJIBHKWk7buis/UDr2O1xcSvy6Fgd60GXIsUf1DnQJ4+H4xj0 -4KlGDfV0OoIu0G4skaMxXDtG6nsEEFZegB31pWXogvziB4xiRfUg3kZwhqG8k9Me -dKZssCz3AwyIDMvUclOGvGBG85hqwvG/Q/lwIHfKN0F5VVJjjVsSn8VoxIidrPIw -q7ejMZdnrY8XD2zHc+0klGvIg5rQmjdJBKuxFshsSUktq6HQjJLyQUp5ISXbY9e2 -nKd+Qmn7OmMCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC -AQYwHQYDVR0OBBYEFNwuH9FhN3nkq9XVsxJxaD1qaJwiMB8GA1UdIwQYMBaAFNwu -H9FhN3nkq9XVsxJxaD1qaJwiMA0GCSqGSIb3DQEBCwUAA4ICAQCR8EICaEDuw2jA -VC/f7GLDw56KoDEoqoOOpFaWEhCGVrqXctJUMHytGdUdaG/7FELYjQ7ztdGl4wJC -XtzoRlgHNQIw4Lx0SsFDKv/bGtCwr2zD/cuz9X9tAy5ZVp0tLTWMstZDFyySCstd -6IwPS3BD0IL/qMy/pJTAvoe9iuOTe8aPmxadJ2W8esVCgmxcB9CpwYhgROmYhRZf -+I/KARDOJcP5YBugxZfD0yyIMaK9MOzQ0MAS8cE54+X1+NZK3TTN+2/BT+MAi1bi -kvcoskJ3ciNnxz8RFbLEAwW+uxF7Cr+obuf/WEPPm2eggAe2HcqtbepBEX4tdJP7 -wry+UUTF72glJ4DjyKDUEuzZpTcdN3y0kcra1LGWge9oXHYQSa9+pTeAsRxSvTOB -TI/53WXZFM2KJVj04sWDpQmQ1GwUY7VA3+vA/MRYfg0UFodUJ25W5HCEuGwyEn6C -MUO+1918oa2u1qsgEu8KwxCMSZY13At1XrFP1U80DhEgB3VDRemjEdqso5nCtnkn -4rnvyOL2NSl6dPrFf4IFYqYK6miyeUcGbvJXqBUzxvd4Sj1Ce2t+/vdG6tHrju+I -aFvowdlxfv1k7/9nR4hYJS8+hge9+6jlgqispdNpQ80xiEmEU5LAsTkbOYMBMMTy -qfrQA71yN2BWHzZ8vTmR9W0Nv3vXkg== ------END CERTIFICATE----- - -# Issuer: CN=ANF Secure Server Root CA O=ANF Autoridad de Certificacion OU=ANF CA Raiz -# Subject: CN=ANF Secure Server Root CA O=ANF Autoridad de Certificacion OU=ANF CA Raiz -# Label: "ANF Secure Server Root CA" -# Serial: 996390341000653745 -# MD5 Fingerprint: 26:a6:44:5a:d9:af:4e:2f:b2:1d:b6:65:b0:4e:e8:96 -# SHA1 Fingerprint: 5b:6e:68:d0:cc:15:b6:a0:5f:1e:c1:5f:ae:02:fc:6b:2f:5d:6f:74 -# SHA256 Fingerprint: fb:8f:ec:75:91:69:b9:10:6b:1e:51:16:44:c6:18:c5:13:04:37:3f:6c:06:43:08:8d:8b:ef:fd:1b:99:75:99 ------BEGIN CERTIFICATE----- -MIIF7zCCA9egAwIBAgIIDdPjvGz5a7EwDQYJKoZIhvcNAQELBQAwgYQxEjAQBgNV -BAUTCUc2MzI4NzUxMDELMAkGA1UEBhMCRVMxJzAlBgNVBAoTHkFORiBBdXRvcmlk -YWQgZGUgQ2VydGlmaWNhY2lvbjEUMBIGA1UECxMLQU5GIENBIFJhaXoxIjAgBgNV -BAMTGUFORiBTZWN1cmUgU2VydmVyIFJvb3QgQ0EwHhcNMTkwOTA0MTAwMDM4WhcN -MzkwODMwMTAwMDM4WjCBhDESMBAGA1UEBRMJRzYzMjg3NTEwMQswCQYDVQQGEwJF -UzEnMCUGA1UEChMeQU5GIEF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uMRQwEgYD -VQQLEwtBTkYgQ0EgUmFpejEiMCAGA1UEAxMZQU5GIFNlY3VyZSBTZXJ2ZXIgUm9v -dCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANvrayvmZFSVgpCj -cqQZAZ2cC4Ffc0m6p6zzBE57lgvsEeBbphzOG9INgxwruJ4dfkUyYA8H6XdYfp9q -yGFOtibBTI3/TO80sh9l2Ll49a2pcbnvT1gdpd50IJeh7WhM3pIXS7yr/2WanvtH -2Vdy8wmhrnZEE26cLUQ5vPnHO6RYPUG9tMJJo8gN0pcvB2VSAKduyK9o7PQUlrZX -H1bDOZ8rbeTzPvY1ZNoMHKGESy9LS+IsJJ1tk0DrtSOOMspvRdOoiXsezx76W0OL -zc2oD2rKDF65nkeP8Nm2CgtYZRczuSPkdxl9y0oukntPLxB3sY0vaJxizOBQ+OyR -p1RMVwnVdmPF6GUe7m1qzwmd+nxPrWAI/VaZDxUse6mAq4xhj0oHdkLePfTdsiQz -W7i1o0TJrH93PB0j7IKppuLIBkwC/qxcmZkLLxCKpvR/1Yd0DVlJRfbwcVw5Kda/ -SiOL9V8BY9KHcyi1Swr1+KuCLH5zJTIdC2MKF4EA/7Z2Xue0sUDKIbvVgFHlSFJn -LNJhiQcND85Cd8BEc5xEUKDbEAotlRyBr+Qc5RQe8TZBAQIvfXOn3kLMTOmJDVb3 -n5HUA8ZsyY/b2BzgQJhdZpmYgG4t/wHFzstGH6wCxkPmrqKEPMVOHj1tyRRM4y5B -u8o5vzY8KhmqQYdOpc5LMnndkEl/AgMBAAGjYzBhMB8GA1UdIwQYMBaAFJxf0Gxj -o1+TypOYCK2Mh6UsXME3MB0GA1UdDgQWBBScX9BsY6Nfk8qTmAitjIelLFzBNzAO -BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC -AgEATh65isagmD9uw2nAalxJUqzLK114OMHVVISfk/CHGT0sZonrDUL8zPB1hT+L -9IBdeeUXZ701guLyPI59WzbLWoAAKfLOKyzxj6ptBZNscsdW699QIyjlRRA96Gej -rw5VD5AJYu9LWaL2U/HANeQvwSS9eS9OICI7/RogsKQOLHDtdD+4E5UGUcjohybK -pFtqFiGS3XNgnhAY3jyB6ugYw3yJ8otQPr0R4hUDqDZ9MwFsSBXXiJCZBMXM5gf0 -vPSQ7RPi6ovDj6MzD8EpTBNO2hVWcXNyglD2mjN8orGoGjR0ZVzO0eurU+AagNjq -OknkJjCb5RyKqKkVMoaZkgoQI1YS4PbOTOK7vtuNknMBZi9iPrJyJ0U27U1W45eZ -/zo1PqVUSlJZS2Db7v54EX9K3BR5YLZrZAPbFYPhor72I5dQ8AkzNqdxliXzuUJ9 -2zg/LFis6ELhDtjTO0wugumDLmsx2d1Hhk9tl5EuT+IocTUW0fJz/iUrB0ckYyfI -+PbZa/wSMVYIwFNCr5zQM378BvAxRAMU8Vjq8moNqRGyg77FGr8H6lnco4g175x2 -MjxNBiLOFeXdntiP2t7SxDnlF4HPOEfrf4htWRvfn0IUrn7PqLBmZdo3r5+qPeoo -tt7VMVgWglvquxl1AnMaykgaIZOQCo6ThKd9OyMYkomgjaw= ------END CERTIFICATE----- - -# Issuer: CN=Certum EC-384 CA O=Asseco Data Systems S.A. OU=Certum Certification Authority -# Subject: CN=Certum EC-384 CA O=Asseco Data Systems S.A. OU=Certum Certification Authority -# Label: "Certum EC-384 CA" -# Serial: 160250656287871593594747141429395092468 -# MD5 Fingerprint: b6:65:b3:96:60:97:12:a1:ec:4e:e1:3d:a3:c6:c9:f1 -# SHA1 Fingerprint: f3:3e:78:3c:ac:df:f4:a2:cc:ac:67:55:69:56:d7:e5:16:3c:e1:ed -# SHA256 Fingerprint: 6b:32:80:85:62:53:18:aa:50:d1:73:c9:8d:8b:da:09:d5:7e:27:41:3d:11:4c:f7:87:a0:f5:d0:6c:03:0c:f6 ------BEGIN CERTIFICATE----- -MIICZTCCAeugAwIBAgIQeI8nXIESUiClBNAt3bpz9DAKBggqhkjOPQQDAzB0MQsw -CQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScw -JQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAXBgNVBAMT -EENlcnR1bSBFQy0zODQgQ0EwHhcNMTgwMzI2MDcyNDU0WhcNNDMwMzI2MDcyNDU0 -WjB0MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBT -LkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAX -BgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATE -KI6rGFtqvm5kN2PkzeyrOvfMobgOgknXhimfoZTy42B4mIF4Bk3y7JoOV2CDn7Tm -Fy8as10CW4kjPMIRBSqniBMY81CE1700LCeJVf/OTOffph8oxPBUw7l8t1Ot68Kj -QjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI0GZnQkdjrzife81r1HfS+8 -EF9LMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNoADBlAjADVS2m5hjEfO/J -UG7BJw+ch69u1RsIGL2SKcHvlJF40jocVYli5RsJHrpka/F2tNQCMQC0QoSZ/6vn -nvuRlydd3LBbMHHOXjgaatkl5+r3YZJW+OraNsKHZZYuciUvf9/DE8k= ------END CERTIFICATE----- - -# Issuer: CN=Certum Trusted Root CA O=Asseco Data Systems S.A. OU=Certum Certification Authority -# Subject: CN=Certum Trusted Root CA O=Asseco Data Systems S.A. OU=Certum Certification Authority -# Label: "Certum Trusted Root CA" -# Serial: 40870380103424195783807378461123655149 -# MD5 Fingerprint: 51:e1:c2:e7:fe:4c:84:af:59:0e:2f:f4:54:6f:ea:29 -# SHA1 Fingerprint: c8:83:44:c0:18:ae:9f:cc:f1:87:b7:8f:22:d1:c5:d7:45:84:ba:e5 -# SHA256 Fingerprint: fe:76:96:57:38:55:77:3e:37:a9:5e:7a:d4:d9:cc:96:c3:01:57:c1:5d:31:76:5b:a9:b1:57:04:e1:ae:78:fd ------BEGIN CERTIFICATE----- -MIIFwDCCA6igAwIBAgIQHr9ZULjJgDdMBvfrVU+17TANBgkqhkiG9w0BAQ0FADB6 -MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEu -MScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxHzAdBgNV -BAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwHhcNMTgwMzE2MTIxMDEzWhcNNDMw -MzE2MTIxMDEzWjB6MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEg -U3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRo -b3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwggIiMA0GCSqG -SIb3DQEBAQUAA4ICDwAwggIKAoICAQDRLY67tzbqbTeRn06TpwXkKQMlzhyC93yZ -n0EGze2jusDbCSzBfN8pfktlL5On1AFrAygYo9idBcEq2EXxkd7fO9CAAozPOA/q -p1x4EaTByIVcJdPTsuclzxFUl6s1wB52HO8AU5853BSlLCIls3Jy/I2z5T4IHhQq -NwuIPMqw9MjCoa68wb4pZ1Xi/K1ZXP69VyywkI3C7Te2fJmItdUDmj0VDT06qKhF -8JVOJVkdzZhpu9PMMsmN74H+rX2Ju7pgE8pllWeg8xn2A1bUatMn4qGtg/BKEiJ3 -HAVz4hlxQsDsdUaakFjgao4rpUYwBI4Zshfjvqm6f1bxJAPXsiEodg42MEx51UGa -mqi4NboMOvJEGyCI98Ul1z3G4z5D3Yf+xOr1Uz5MZf87Sst4WmsXXw3Hw09Omiqi -7VdNIuJGmj8PkTQkfVXjjJU30xrwCSss0smNtA0Aq2cpKNgB9RkEth2+dv5yXMSF -ytKAQd8FqKPVhJBPC/PgP5sZ0jeJP/J7UhyM9uH3PAeXjA6iWYEMspA90+NZRu0P -qafegGtaqge2Gcu8V/OXIXoMsSt0Puvap2ctTMSYnjYJdmZm/Bo/6khUHL4wvYBQ -v3y1zgD2DGHZ5yQD4OMBgQ692IU0iL2yNqh7XAjlRICMb/gv1SHKHRzQ+8S1h9E6 -Tsd2tTVItQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSM+xx1 -vALTn04uSNn5YFSqxLNP+jAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQENBQAD -ggIBAEii1QALLtA/vBzVtVRJHlpr9OTy4EA34MwUe7nJ+jW1dReTagVphZzNTxl4 -WxmB82M+w85bj/UvXgF2Ez8sALnNllI5SW0ETsXpD4YN4fqzX4IS8TrOZgYkNCvo -zMrnadyHncI013nR03e4qllY/p0m+jiGPp2Kh2RX5Rc64vmNueMzeMGQ2Ljdt4NR -5MTMI9UGfOZR0800McD2RrsLrfw9EAUqO0qRJe6M1ISHgCq8CYyqOhNf6DR5UMEQ -GfnTKB7U0VEwKbOukGfWHwpjscWpxkIxYxeU72nLL/qMFH3EQxiJ2fAyQOaA4kZf -5ePBAFmo+eggvIksDkc0C+pXwlM2/KfUrzHN/gLldfq5Jwn58/U7yn2fqSLLiMmq -0Uc9NneoWWRrJ8/vJ8HjJLWG965+Mk2weWjROeiQWMODvA8s1pfrzgzhIMfatz7D -P78v3DSk+yshzWePS/Tj6tQ/50+6uaWTRRxmHyH6ZF5v4HaUMst19W7l9o/HuKTM -qJZ9ZPskWkoDbGs4xugDQ5r3V7mzKWmTOPQD8rv7gmsHINFSH5pkAnuYZttcTVoP -0ISVoDwUQwbKytu4QTbaakRnh6+v40URFWkIsr4WOZckbxJF0WddCajJFdr60qZf -E2Efv4WstK2tBZQIgx51F9NxO5NQI1mg7TyRVJ12AMXDuDjb ------END CERTIFICATE----- - -# Issuer: CN=TunTrust Root CA O=Agence Nationale de Certification Electronique -# Subject: CN=TunTrust Root CA O=Agence Nationale de Certification Electronique -# Label: "TunTrust Root CA" -# Serial: 108534058042236574382096126452369648152337120275 -# MD5 Fingerprint: 85:13:b9:90:5b:36:5c:b6:5e:b8:5a:f8:e0:31:57:b4 -# SHA1 Fingerprint: cf:e9:70:84:0f:e0:73:0f:9d:f6:0c:7f:2c:4b:ee:20:46:34:9c:bb -# SHA256 Fingerprint: 2e:44:10:2a:b5:8c:b8:54:19:45:1c:8e:19:d9:ac:f3:66:2c:af:bc:61:4b:6a:53:96:0a:30:f7:d0:e2:eb:41 ------BEGIN CERTIFICATE----- -MIIFszCCA5ugAwIBAgIUEwLV4kBMkkaGFmddtLu7sms+/BMwDQYJKoZIhvcNAQEL -BQAwYTELMAkGA1UEBhMCVE4xNzA1BgNVBAoMLkFnZW5jZSBOYXRpb25hbGUgZGUg -Q2VydGlmaWNhdGlvbiBFbGVjdHJvbmlxdWUxGTAXBgNVBAMMEFR1blRydXN0IFJv -b3QgQ0EwHhcNMTkwNDI2MDg1NzU2WhcNNDQwNDI2MDg1NzU2WjBhMQswCQYDVQQG -EwJUTjE3MDUGA1UECgwuQWdlbmNlIE5hdGlvbmFsZSBkZSBDZXJ0aWZpY2F0aW9u -IEVsZWN0cm9uaXF1ZTEZMBcGA1UEAwwQVHVuVHJ1c3QgUm9vdCBDQTCCAiIwDQYJ -KoZIhvcNAQEBBQADggIPADCCAgoCggIBAMPN0/y9BFPdDCA61YguBUtB9YOCfvdZ -n56eY+hz2vYGqU8ftPkLHzmMmiDQfgbU7DTZhrx1W4eI8NLZ1KMKsmwb60ksPqxd -2JQDoOw05TDENX37Jk0bbjBU2PWARZw5rZzJJQRNmpA+TkBuimvNKWfGzC3gdOgF -VwpIUPp6Q9p+7FuaDmJ2/uqdHYVy7BG7NegfJ7/Boce7SBbdVtfMTqDhuazb1YMZ -GoXRlJfXyqNlC/M4+QKu3fZnz8k/9YosRxqZbwUN/dAdgjH8KcwAWJeRTIAAHDOF -li/LQcKLEITDCSSJH7UP2dl3RxiSlGBcx5kDPP73lad9UKGAwqmDrViWVSHbhlnU -r8a83YFuB9tgYv7sEG7aaAH0gxupPqJbI9dkxt/con3YS7qC0lH4Zr8GRuR5KiY2 -eY8fTpkdso8MDhz/yV3A/ZAQprE38806JG60hZC/gLkMjNWb1sjxVj8agIl6qeIb -MlEsPvLfe/ZdeikZjuXIvTZxi11Mwh0/rViizz1wTaZQmCXcI/m4WEEIcb9PuISg -jwBUFfyRbVinljvrS5YnzWuioYasDXxU5mZMZl+QviGaAkYt5IPCgLnPSz7ofzwB -7I9ezX/SKEIBlYrilz0QIX32nRzFNKHsLA4KUiwSVXAkPcvCFDVDXSdOvsC9qnyW -5/yeYa1E0wCXAgMBAAGjYzBhMB0GA1UdDgQWBBQGmpsfU33x9aTI04Y+oXNZtPdE -ITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFAaamx9TffH1pMjThj6hc1m0 -90QhMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAqgVutt0Vyb+z -xiD2BkewhpMl0425yAA/l/VSJ4hxyXT968pk21vvHl26v9Hr7lxpuhbI87mP0zYu -QEkHDVneixCwSQXi/5E/S7fdAo74gShczNxtr18UnH1YeA32gAm56Q6XKRm4t+v4 -FstVEuTGfbvE7Pi1HE4+Z7/FXxttbUcoqgRYYdZ2vyJ/0Adqp2RT8JeNnYA/u8EH -22Wv5psymsNUk8QcCMNE+3tjEUPRahphanltkE8pjkcFwRJpadbGNjHh/PqAulxP -xOu3Mqz4dWEX1xAZufHSCe96Qp1bWgvUxpVOKs7/B9dPfhgGiPEZtdmYu65xxBzn -dFlY7wyJz4sfdZMaBBSSSFCp61cpABbjNhzI+L/wM9VBD8TMPN3pM0MBkRArHtG5 -Xc0yGYuPjCB31yLEQtyEFpslbei0VXF/sHyz03FJuc9SpAQ/3D2gu68zngowYI7b -nV2UqL1g52KAdoGDDIzMMEZJ4gzSqK/rYXHv5yJiqfdcZGyfFoxnNidF9Ql7v/YQ -CvGwjVRDjAS6oz/v4jXH+XTgbzRB0L9zZVcg+ZtnemZoJE6AZb0QmQZZ8mWvuMZH -u/2QeItBcy6vVR/cO5JyboTT0GFMDcx2V+IthSIVNg3rAZ3r2OvEhJn7wAzMMujj -d9qDRIueVSjAi1jTkD5OGwDxFa2DK5o= ------END CERTIFICATE----- - -# Issuer: CN=HARICA TLS RSA Root CA 2021 O=Hellenic Academic and Research Institutions CA -# Subject: CN=HARICA TLS RSA Root CA 2021 O=Hellenic Academic and Research Institutions CA -# Label: "HARICA TLS RSA Root CA 2021" -# Serial: 76817823531813593706434026085292783742 -# MD5 Fingerprint: 65:47:9b:58:86:dd:2c:f0:fc:a2:84:1f:1e:96:c4:91 -# SHA1 Fingerprint: 02:2d:05:82:fa:88:ce:14:0c:06:79:de:7f:14:10:e9:45:d7:a5:6d -# SHA256 Fingerprint: d9:5d:0e:8e:da:79:52:5b:f9:be:b1:1b:14:d2:10:0d:32:94:98:5f:0c:62:d9:fa:bd:9c:d9:99:ec:cb:7b:1d ------BEGIN CERTIFICATE----- -MIIFpDCCA4ygAwIBAgIQOcqTHO9D88aOk8f0ZIk4fjANBgkqhkiG9w0BAQsFADBs -MQswCQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl -c2VhcmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBSU0Eg -Um9vdCBDQSAyMDIxMB4XDTIxMDIxOTEwNTUzOFoXDTQ1MDIxMzEwNTUzN1owbDEL -MAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNl -YXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgUlNBIFJv -b3QgQ0EgMjAyMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIvC569l -mwVnlskNJLnQDmT8zuIkGCyEf3dRywQRNrhe7Wlxp57kJQmXZ8FHws+RFjZiPTgE -4VGC/6zStGndLuwRo0Xua2s7TL+MjaQenRG56Tj5eg4MmOIjHdFOY9TnuEFE+2uv -a9of08WRiFukiZLRgeaMOVig1mlDqa2YUlhu2wr7a89o+uOkXjpFc5gH6l8Cct4M -pbOfrqkdtx2z/IpZ525yZa31MJQjB/OCFks1mJxTuy/K5FrZx40d/JiZ+yykgmvw -Kh+OC19xXFyuQnspiYHLA6OZyoieC0AJQTPb5lh6/a6ZcMBaD9YThnEvdmn8kN3b -LW7R8pv1GmuebxWMevBLKKAiOIAkbDakO/IwkfN4E8/BPzWr8R0RI7VDIp4BkrcY -AuUR0YLbFQDMYTfBKnya4dC6s1BG7oKsnTH4+yPiAwBIcKMJJnkVU2DzOFytOOqB -AGMUuTNe3QvboEUHGjMJ+E20pwKmafTCWQWIZYVWrkvL4N48fS0ayOn7H6NhStYq -E613TBoYm5EPWNgGVMWX+Ko/IIqmhaZ39qb8HOLubpQzKoNQhArlT4b4UEV4AIHr -W2jjJo3Me1xR9BQsQL4aYB16cmEdH2MtiKrOokWQCPxrvrNQKlr9qEgYRtaQQJKQ -CoReaDH46+0N0x3GfZkYVVYnZS6NRcUk7M7jAgMBAAGjQjBAMA8GA1UdEwEB/wQF -MAMBAf8wHQYDVR0OBBYEFApII6ZgpJIKM+qTW8VX6iVNvRLuMA4GA1UdDwEB/wQE -AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAPpBIqm5iFSVmewzVjIuJndftTgfvnNAU -X15QvWiWkKQUEapobQk1OUAJ2vQJLDSle1mESSmXdMgHHkdt8s4cUCbjnj1AUz/3 -f5Z2EMVGpdAgS1D0NTsY9FVqQRtHBmg8uwkIYtlfVUKqrFOFrJVWNlar5AWMxaja -H6NpvVMPxP/cyuN+8kyIhkdGGvMA9YCRotxDQpSbIPDRzbLrLFPCU3hKTwSUQZqP -JzLB5UkZv/HywouoCjkxKLR9YjYsTewfM7Z+d21+UPCfDtcRj88YxeMn/ibvBZ3P -zzfF0HvaO7AWhAw6k9a+F9sPPg4ZeAnHqQJyIkv3N3a6dcSFA1pj1bF1BcK5vZSt -jBWZp5N99sXzqnTPBIWUmAD04vnKJGW/4GKvyMX6ssmeVkjaef2WdhW+o45WxLM0 -/L5H9MG0qPzVMIho7suuyWPEdr6sOBjhXlzPrjoiUevRi7PzKzMHVIf6tLITe7pT -BGIBnfHAT+7hOtSLIBD6Alfm78ELt5BGnBkpjNxvoEppaZS3JGWg/6w/zgH7IS79 -aPib8qXPMThcFarmlwDB31qlpzmq6YR/PFGoOtmUW4y/Twhx5duoXNTSpv4Ao8YW -xw/ogM4cKGR0GQjTQuPOAF1/sdwTsOEFy9EgqoZ0njnnkf3/W9b3raYvAwtt41dU -63ZTGI0RmLo= ------END CERTIFICATE----- - -# Issuer: CN=HARICA TLS ECC Root CA 2021 O=Hellenic Academic and Research Institutions CA -# Subject: CN=HARICA TLS ECC Root CA 2021 O=Hellenic Academic and Research Institutions CA -# Label: "HARICA TLS ECC Root CA 2021" -# Serial: 137515985548005187474074462014555733966 -# MD5 Fingerprint: ae:f7:4c:e5:66:35:d1:b7:9b:8c:22:93:74:d3:4b:b0 -# SHA1 Fingerprint: bc:b0:c1:9d:e9:98:92:70:19:38:57:e9:8d:a7:b4:5d:6e:ee:01:48 -# SHA256 Fingerprint: 3f:99:cc:47:4a:cf:ce:4d:fe:d5:87:94:66:5e:47:8d:15:47:73:9f:2e:78:0f:1b:b4:ca:9b:13:30:97:d4:01 ------BEGIN CERTIFICATE----- -MIICVDCCAdugAwIBAgIQZ3SdjXfYO2rbIvT/WeK/zjAKBggqhkjOPQQDAzBsMQsw -CQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2Vh -cmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBFQ0MgUm9v -dCBDQSAyMDIxMB4XDTIxMDIxOTExMDExMFoXDTQ1MDIxMzExMDEwOVowbDELMAkG -A1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJj -aCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgRUNDIFJvb3Qg -Q0EgMjAyMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDgI/rGgltJ6rK9JOtDA4MM7 -KKrxcm1lAEeIhPyaJmuqS7psBAqIXhfyVYf8MLA04jRYVxqEU+kw2anylnTDUR9Y -STHMmE5gEYd103KUkE+bECUqqHgtvpBBWJAVcqeht6NCMEAwDwYDVR0TAQH/BAUw -AwEB/zAdBgNVHQ4EFgQUyRtTgRL+BNUW0aq8mm+3oJUZbsowDgYDVR0PAQH/BAQD -AgGGMAoGCCqGSM49BAMDA2cAMGQCMBHervjcToiwqfAircJRQO9gcS3ujwLEXQNw -SaSS6sUUiHCm0w2wqsosQJz76YJumgIwK0eaB8bRwoF8yguWGEEbo/QwCZ61IygN -nxS2PFOiTAZpffpskcYqSUXm7LcT4Tps ------END CERTIFICATE----- - -# Issuer: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068 -# Subject: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068 -# Label: "Autoridad de Certificacion Firmaprofesional CIF A62634068" -# Serial: 1977337328857672817 -# MD5 Fingerprint: 4e:6e:9b:54:4c:ca:b7:fa:48:e4:90:b1:15:4b:1c:a3 -# SHA1 Fingerprint: 0b:be:c2:27:22:49:cb:39:aa:db:35:5c:53:e3:8c:ae:78:ff:b6:fe -# SHA256 Fingerprint: 57:de:05:83:ef:d2:b2:6e:03:61:da:99:da:9d:f4:64:8d:ef:7e:e8:44:1c:3b:72:8a:fa:9b:cd:e0:f9:b2:6a ------BEGIN CERTIFICATE----- -MIIGFDCCA/ygAwIBAgIIG3Dp0v+ubHEwDQYJKoZIhvcNAQELBQAwUTELMAkGA1UE -BhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h -cHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0xNDA5MjMxNTIyMDdaFw0zNjA1 -MDUxNTIyMDdaMFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUg -Q2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjgwggIi -MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDDUtd9 -thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQM -cas9UX4PB99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefG -L9ItWY16Ck6WaVICqjaY7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15i -NA9wBj4gGFrO93IbJWyTdBSTo3OxDqqHECNZXyAFGUftaI6SEspd/NYrspI8IM/h -X68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyIplD9amML9ZMWGxmPsu2b -m8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctXMbScyJCy -Z/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirja -EbsXLZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/T -KI8xWVvTyQKmtFLKbpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF -6NkBiDkal4ZkQdU7hwxu+g/GvUgUvzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVh -OSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMB0GA1UdDgQWBBRlzeurNR4APn7VdMAc -tHNHDhpkLzASBgNVHRMBAf8ECDAGAQH/AgEBMIGmBgNVHSAEgZ4wgZswgZgGBFUd -IAAwgY8wLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuZmlybWFwcm9mZXNpb25hbC5j -b20vY3BzMFwGCCsGAQUFBwICMFAeTgBQAGEAcwBlAG8AIABkAGUAIABsAGEAIABC -AG8AbgBhAG4AbwB2AGEAIAA0ADcAIABCAGEAcgBjAGUAbABvAG4AYQAgADAAOAAw -ADEANzAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQADggIBAHSHKAIrdx9m -iWTtj3QuRhy7qPj4Cx2Dtjqn6EWKB7fgPiDL4QjbEwj4KKE1soCzC1HA01aajTNF -Sa9J8OA9B3pFE1r/yJfY0xgsfZb43aJlQ3CTkBW6kN/oGbDbLIpgD7dvlAceHabJ -hfa9NPhAeGIQcDq+fUs5gakQ1JZBu/hfHAsdCPKxsIl68veg4MSPi3i1O1ilI45P -Vf42O+AMt8oqMEEgtIDNrvx2ZnOorm7hfNoD6JQg5iKj0B+QXSBTFCZX2lSX3xZE -EAEeiGaPcjiT3SC3NL7X8e5jjkd5KAb881lFJWAiMxujX6i6KtoaPc1A6ozuBRWV -1aUsIC+nmCjuRfzxuIgALI9C2lHVnOUTaHFFQ4ueCyE8S1wF3BqfmI7avSKecs2t -CsvMo2ebKHTEm9caPARYpoKdrcd7b/+Alun4jWq9GJAd/0kakFI3ky88Al2CdgtR -5xbHV/g4+afNmyJU72OwFW1TZQNKXkqgsqeOSQBZONXH9IBk9W6VULgRfhVwOEqw -f9DEMnDAGf/JOC0ULGb0QkTmVXYbgBVX/8Cnp6o5qtjTcNAuuuuUavpfNIbnYrX9 -ivAwhZTJryQCL2/W3Wf+47BVTwSYT6RBVuKT0Gro1vP7ZeDOdcQxWQzugsgMYDNK -GbqEZycPvEJdvSRUDewdcAZfpLz6IHxV ------END CERTIFICATE----- - -# Issuer: CN=vTrus ECC Root CA O=iTrusChina Co.,Ltd. -# Subject: CN=vTrus ECC Root CA O=iTrusChina Co.,Ltd. -# Label: "vTrus ECC Root CA" -# Serial: 630369271402956006249506845124680065938238527194 -# MD5 Fingerprint: de:4b:c1:f5:52:8c:9b:43:e1:3e:8f:55:54:17:8d:85 -# SHA1 Fingerprint: f6:9c:db:b0:fc:f6:02:13:b6:52:32:a6:a3:91:3f:16:70:da:c3:e1 -# SHA256 Fingerprint: 30:fb:ba:2c:32:23:8e:2a:98:54:7a:f9:79:31:e5:50:42:8b:9b:3f:1c:8e:eb:66:33:dc:fa:86:c5:b2:7d:d3 ------BEGIN CERTIFICATE----- -MIICDzCCAZWgAwIBAgIUbmq8WapTvpg5Z6LSa6Q75m0c1towCgYIKoZIzj0EAwMw -RzELMAkGA1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xGjAY -BgNVBAMTEXZUcnVzIEVDQyBSb290IENBMB4XDTE4MDczMTA3MjY0NFoXDTQzMDcz -MTA3MjY0NFowRzELMAkGA1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28u -LEx0ZC4xGjAYBgNVBAMTEXZUcnVzIEVDQyBSb290IENBMHYwEAYHKoZIzj0CAQYF -K4EEACIDYgAEZVBKrox5lkqqHAjDo6LN/llWQXf9JpRCux3NCNtzslt188+cToL0 -v/hhJoVs1oVbcnDS/dtitN9Ti72xRFhiQgnH+n9bEOf+QP3A2MMrMudwpremIFUd -e4BdS49nTPEQo0IwQDAdBgNVHQ4EFgQUmDnNvtiyjPeyq+GtJK97fKHbH88wDwYD -VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwCgYIKoZIzj0EAwMDaAAwZQIw -V53dVvHH4+m4SVBrm2nDb+zDfSXkV5UTQJtS0zvzQBm8JsctBp61ezaf9SXUY2sA -AjEA6dPGnlaaKsyh2j/IZivTWJwghfqrkYpwcBE4YGQLYgmRWAD5Tfs0aNoJrSEG -GJTO ------END CERTIFICATE----- - -# Issuer: CN=vTrus Root CA O=iTrusChina Co.,Ltd. -# Subject: CN=vTrus Root CA O=iTrusChina Co.,Ltd. -# Label: "vTrus Root CA" -# Serial: 387574501246983434957692974888460947164905180485 -# MD5 Fingerprint: b8:c9:37:df:fa:6b:31:84:64:c5:ea:11:6a:1b:75:fc -# SHA1 Fingerprint: 84:1a:69:fb:f5:cd:1a:25:34:13:3d:e3:f8:fc:b8:99:d0:c9:14:b7 -# SHA256 Fingerprint: 8a:71:de:65:59:33:6f:42:6c:26:e5:38:80:d0:0d:88:a1:8d:a4:c6:a9:1f:0d:cb:61:94:e2:06:c5:c9:63:87 ------BEGIN CERTIFICATE----- -MIIFVjCCAz6gAwIBAgIUQ+NxE9izWRRdt86M/TX9b7wFjUUwDQYJKoZIhvcNAQEL -BQAwQzELMAkGA1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4x -FjAUBgNVBAMTDXZUcnVzIFJvb3QgQ0EwHhcNMTgwNzMxMDcyNDA1WhcNNDMwNzMx -MDcyNDA1WjBDMQswCQYDVQQGEwJDTjEcMBoGA1UEChMTaVRydXNDaGluYSBDby4s -THRkLjEWMBQGA1UEAxMNdlRydXMgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQAD -ggIPADCCAgoCggIBAL1VfGHTuB0EYgWgrmy3cLRB6ksDXhA/kFocizuwZotsSKYc -IrrVQJLuM7IjWcmOvFjai57QGfIvWcaMY1q6n6MLsLOaXLoRuBLpDLvPbmyAhykU -AyyNJJrIZIO1aqwTLDPxn9wsYTwaP3BVm60AUn/PBLn+NvqcwBauYv6WTEN+VRS+ -GrPSbcKvdmaVayqwlHeFXgQPYh1jdfdr58tbmnDsPmcF8P4HCIDPKNsFxhQnL4Z9 -8Cfe/+Z+M0jnCx5Y0ScrUw5XSmXX+6KAYPxMvDVTAWqXcoKv8R1w6Jz1717CbMdH -flqUhSZNO7rrTOiwCcJlwp2dCZtOtZcFrPUGoPc2BX70kLJrxLT5ZOrpGgrIDajt -J8nU57O5q4IikCc9Kuh8kO+8T/3iCiSn3mUkpF3qwHYw03dQ+A0Em5Q2AXPKBlim -0zvc+gRGE1WKyURHuFE5Gi7oNOJ5y1lKCn+8pu8fA2dqWSslYpPZUxlmPCdiKYZN -pGvu/9ROutW04o5IWgAZCfEF2c6Rsffr6TlP9m8EQ5pV9T4FFL2/s1m02I4zhKOQ -UqqzApVg+QxMaPnu1RcN+HFXtSXkKe5lXa/R7jwXC1pDxaWG6iSe4gUH3DRCEpHW -OXSuTEGC2/KmSNGzm/MzqvOmwMVO9fSddmPmAsYiS8GVP1BkLFTltvA8Kc9XAgMB -AAGjQjBAMB0GA1UdDgQWBBRUYnBj8XWEQ1iO0RYgscasGrz2iTAPBgNVHRMBAf8E -BTADAQH/MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAKbqSSaet -8PFww+SX8J+pJdVrnjT+5hpk9jprUrIQeBqfTNqK2uwcN1LgQkv7bHbKJAs5EhWd -nxEt/Hlk3ODg9d3gV8mlsnZwUKT+twpw1aA08XXXTUm6EdGz2OyC/+sOxL9kLX1j -bhd47F18iMjrjld22VkE+rxSH0Ws8HqA7Oxvdq6R2xCOBNyS36D25q5J08FsEhvM -Kar5CKXiNxTKsbhm7xqC5PD48acWabfbqWE8n/Uxy+QARsIvdLGx14HuqCaVvIiv -TDUHKgLKeBRtRytAVunLKmChZwOgzoy8sHJnxDHO2zTlJQNgJXtxmOTAGytfdELS -S8VZCAeHvsXDf+eW2eHcKJfWjwXj9ZtOyh1QRwVTsMo554WgicEFOwE30z9J4nfr -I8iIZjs9OXYhRvHsXyO466JmdXTBQPfYaJqT4i2pLr0cox7IdMakLXogqzu4sEb9 -b91fUlV1YvCXoHzXOP0l382gmxDPi7g4Xl7FtKYCNqEeXxzP4padKar9mK5S4fNB -UvupLnKWnyfjqnN9+BojZns7q2WwMgFLFT49ok8MKzWixtlnEjUwzXYuFrOZnk1P -Ti07NEPhmg4NpGaXutIcSkwsKouLgU9xGqndXHt7CMUADTdA43x7VF8vhV929ven -sBxXVsFy6K2ir40zSbofitzmdHxghm+Hl3s= ------END CERTIFICATE----- - -# Issuer: CN=ISRG Root X2 O=Internet Security Research Group -# Subject: CN=ISRG Root X2 O=Internet Security Research Group -# Label: "ISRG Root X2" -# Serial: 87493402998870891108772069816698636114 -# MD5 Fingerprint: d3:9e:c4:1e:23:3c:a6:df:cf:a3:7e:6d:e0:14:e6:e5 -# SHA1 Fingerprint: bd:b1:b9:3c:d5:97:8d:45:c6:26:14:55:f8:db:95:c7:5a:d1:53:af -# SHA256 Fingerprint: 69:72:9b:8e:15:a8:6e:fc:17:7a:57:af:b7:17:1d:fc:64:ad:d2:8c:2f:ca:8c:f1:50:7e:34:45:3c:cb:14:70 ------BEGIN CERTIFICATE----- -MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw -CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg -R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00 -MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT -ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw -EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW -+1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9 -ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T -AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI -zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW -tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1 -/q4AaOeMSQ+2b1tbFfLn ------END CERTIFICATE----- - -# Issuer: CN=HiPKI Root CA - G1 O=Chunghwa Telecom Co., Ltd. -# Subject: CN=HiPKI Root CA - G1 O=Chunghwa Telecom Co., Ltd. -# Label: "HiPKI Root CA - G1" -# Serial: 60966262342023497858655262305426234976 -# MD5 Fingerprint: 69:45:df:16:65:4b:e8:68:9a:8f:76:5f:ff:80:9e:d3 -# SHA1 Fingerprint: 6a:92:e4:a8:ee:1b:ec:96:45:37:e3:29:57:49:cd:96:e3:e5:d2:60 -# SHA256 Fingerprint: f0:15:ce:3c:c2:39:bf:ef:06:4b:e9:f1:d2:c4:17:e1:a0:26:4a:0a:94:be:1f:0c:8d:12:18:64:eb:69:49:cc ------BEGIN CERTIFICATE----- -MIIFajCCA1KgAwIBAgIQLd2szmKXlKFD6LDNdmpeYDANBgkqhkiG9w0BAQsFADBP -MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0 -ZC4xGzAZBgNVBAMMEkhpUEtJIFJvb3QgQ0EgLSBHMTAeFw0xOTAyMjIwOTQ2MDRa -Fw0zNzEyMzExNTU5NTlaME8xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3 -YSBUZWxlY29tIENvLiwgTHRkLjEbMBkGA1UEAwwSSGlQS0kgUm9vdCBDQSAtIEcx -MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA9B5/UnMyDHPkvRN0o9Qw -qNCuS9i233VHZvR85zkEHmpwINJaR3JnVfSl6J3VHiGh8Ge6zCFovkRTv4354twv -Vcg3Px+kwJyz5HdcoEb+d/oaoDjq7Zpy3iu9lFc6uux55199QmQ5eiY29yTw1S+6 -lZgRZq2XNdZ1AYDgr/SEYYwNHl98h5ZeQa/rh+r4XfEuiAU+TCK72h8q3VJGZDnz -Qs7ZngyzsHeXZJzA9KMuH5UHsBffMNsAGJZMoYFL3QRtU6M9/Aes1MU3guvklQgZ -KILSQjqj2FPseYlgSGDIcpJQ3AOPgz+yQlda22rpEZfdhSi8MEyr48KxRURHH+CK -FgeW0iEPU8DtqX7UTuybCeyvQqww1r/REEXgphaypcXTT3OUM3ECoWqj1jOXTyFj -HluP2cFeRXF3D4FdXyGarYPM+l7WjSNfGz1BryB1ZlpK9p/7qxj3ccC2HTHsOyDr -y+K49a6SsvfhhEvyovKTmiKe0xRvNlS9H15ZFblzqMF8b3ti6RZsR1pl8w4Rm0bZ -/W3c1pzAtH2lsN0/Vm+h+fbkEkj9Bn8SV7apI09bA8PgcSojt/ewsTu8mL3WmKgM -a/aOEmem8rJY5AIJEzypuxC00jBF8ez3ABHfZfjcK0NVvxaXxA/VLGGEqnKG/uY6 -fsI/fe78LxQ+5oXdUG+3Se0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNV -HQ4EFgQU8ncX+l6o/vY9cdVouslGDDjYr7AwDgYDVR0PAQH/BAQDAgGGMA0GCSqG -SIb3DQEBCwUAA4ICAQBQUfB13HAE4/+qddRxosuej6ip0691x1TPOhwEmSKsxBHi -7zNKpiMdDg1H2DfHb680f0+BazVP6XKlMeJ45/dOlBhbQH3PayFUhuaVevvGyuqc -SE5XCV0vrPSltJczWNWseanMX/mF+lLFjfiRFOs6DRfQUsJ748JzjkZ4Bjgs6Fza -ZsT0pPBWGTMpWmWSBUdGSquEwx4noR8RkpkndZMPvDY7l1ePJlsMu5wP1G4wB9Tc -XzZoZjmDlicmisjEOf6aIW/Vcobpf2Lll07QJNBAsNB1CI69aO4I1258EHBGG3zg -iLKecoaZAeO/n0kZtCW+VmWuF2PlHt/o/0elv+EmBYTksMCv5wiZqAxeJoBF1Pho -L5aPruJKHJwWDBNvOIf2u8g0X5IDUXlwpt/L9ZlNec1OvFefQ05rLisY+GpzjLrF -Ne85akEez3GoorKGB1s6yeHvP2UEgEcyRHCVTjFnanRbEEV16rCf0OY1/k6fi8wr -kkVbbiVghUbN0aqwdmaTd5a+g744tiROJgvM7XpWGuDpWsZkrUx6AEhEL7lAuxM+ -vhV4nYWBSipX3tUZQ9rbyltHhoMLP7YNdnhzeSJesYAfz77RP1YQmCuVh6EfnWQU -YDksswBVLuT1sw5XxJFBAJw/6KXf6vb/yPCtbVKoF6ubYfwSUTXkJf2vqmqGOQ== ------END CERTIFICATE----- - -# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R4 -# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R4 -# Label: "GlobalSign ECC Root CA - R4" -# Serial: 159662223612894884239637590694 -# MD5 Fingerprint: 26:29:f8:6d:e1:88:bf:a2:65:7f:aa:c4:cd:0f:7f:fc -# SHA1 Fingerprint: 6b:a0:b0:98:e1:71:ef:5a:ad:fe:48:15:80:77:10:f4:bd:6f:0b:28 -# SHA256 Fingerprint: b0:85:d7:0b:96:4f:19:1a:73:e4:af:0d:54:ae:7a:0e:07:aa:fd:af:9b:71:dd:08:62:13:8a:b7:32:5a:24:a2 ------BEGIN CERTIFICATE----- -MIIB3DCCAYOgAwIBAgINAgPlfvU/k/2lCSGypjAKBggqhkjOPQQDAjBQMSQwIgYD -VQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2Jh -bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTIxMTEzMDAwMDAwWhcNMzgw -MTE5MDMxNDA3WjBQMSQwIgYDVQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0g -UjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wWTAT -BgcqhkjOPQIBBggqhkjOPQMBBwNCAAS4xnnTj2wlDp8uORkcA6SumuU5BwkWymOx -uYb4ilfBV85C+nOh92VC/x7BALJucw7/xyHlGKSq2XE/qNS5zowdo0IwQDAOBgNV -HQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVLB7rUW44kB/ -+wpu+74zyTyjhNUwCgYIKoZIzj0EAwIDRwAwRAIgIk90crlgr/HmnKAWBVBfw147 -bmF0774BxL4YSFlhgjICICadVGNA3jdgUM/I2O2dgq43mLyjj0xMqTQrbO/7lZsm ------END CERTIFICATE----- - -# Issuer: CN=GTS Root R1 O=Google Trust Services LLC -# Subject: CN=GTS Root R1 O=Google Trust Services LLC -# Label: "GTS Root R1" -# Serial: 159662320309726417404178440727 -# MD5 Fingerprint: 05:fe:d0:bf:71:a8:a3:76:63:da:01:e0:d8:52:dc:40 -# SHA1 Fingerprint: e5:8c:1c:c4:91:3b:38:63:4b:e9:10:6e:e3:ad:8e:6b:9d:d9:81:4a -# SHA256 Fingerprint: d9:47:43:2a:bd:e7:b7:fa:90:fc:2e:6b:59:10:1b:12:80:e0:e1:c7:e4:e4:0f:a3:c6:88:7f:ff:57:a7:f4:cf ------BEGIN CERTIFICATE----- -MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQsw -CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU -MBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw -MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp -Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEBAQUA -A4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaMf/vo -27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7w -Cl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjw -TcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0Pfybl -qAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtcvfaH -szVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4Zor8 -Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUspzBmk -MiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92 -wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70p -aDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrN -VjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQID -AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E -FgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBAJ+qQibb -C5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmmWJyq+bXmYOfg6LEe -QkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuy -h6f88/qBVRRiClmpIgUxPoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM4 -7HLwEXWdyzRSjeZ2axfG34arJ45JK3VmgRAhpuo+9K4l/3wV3s6MJT/KYnAK9y8J -ZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYciNuaCp+0KueIHoI17eko8cdLiA6Ef -MgfdG+RCzgwARWGAtQsgWSl4vflVy2PFPEz0tv/bal8xa5meLMFrUKTX5hgUvYU/ -Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJFfbdT -6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ -0E6yove+7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm -2tIMPNuzjsmhDYAPexZ3FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bb -bP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3gm3c ------END CERTIFICATE----- - -# Issuer: CN=GTS Root R2 O=Google Trust Services LLC -# Subject: CN=GTS Root R2 O=Google Trust Services LLC -# Label: "GTS Root R2" -# Serial: 159662449406622349769042896298 -# MD5 Fingerprint: 1e:39:c0:53:e6:1e:29:82:0b:ca:52:55:36:5d:57:dc -# SHA1 Fingerprint: 9a:44:49:76:32:db:de:fa:d0:bc:fb:5a:7b:17:bd:9e:56:09:24:94 -# SHA256 Fingerprint: 8d:25:cd:97:22:9d:bf:70:35:6b:da:4e:b3:cc:73:40:31:e2:4c:f0:0f:af:cf:d3:2d:c7:6e:b5:84:1c:7e:a8 ------BEGIN CERTIFICATE----- -MIIFVzCCAz+gAwIBAgINAgPlrsWNBCUaqxElqjANBgkqhkiG9w0BAQwFADBHMQsw -CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU -MBIGA1UEAxMLR1RTIFJvb3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw -MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp -Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUA -A4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3LvCvpt -nfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3KgGjSY -6Dlo7JUle3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9BuXvAu -MC6C/Pq8tBcKSOWIm8Wba96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOdre7k -RXuJVfeKH2JShBKzwkCX44ofR5GmdFrS+LFjKBC4swm4VndAoiaYecb+3yXuPuWg -f9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7MkogwTZq9TwtImoS1mKPV -+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJGr61K8Yzo -dDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqjx5RW -Ir9qS34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsRnTKa -G73VululycslaVNVJ1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0kzCq -gc7dGtxRcw1PcOnlthYhGXmy5okLdWTK1au8CcEYof/UVKGFPP0UJAOyh9OktwID -AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E -FgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQADggIBAB/Kzt3H -vqGf2SdMC9wXmBFqiN495nFWcrKeGk6c1SuYJF2ba3uwM4IJvd8lRuqYnrYb/oM8 -0mJhwQTtzuDFycgTE1XnqGOtjHsB/ncw4c5omwX4Eu55MaBBRTUoCnGkJE+M3DyC -B19m3H0Q/gxhswWV7uGugQ+o+MePTagjAiZrHYNSVc61LwDKgEDg4XSsYPWHgJ2u -NmSRXbBoGOqKYcl3qJfEycel/FVL8/B/uWU9J2jQzGv6U53hkRrJXRqWbTKH7QMg -yALOWr7Z6v2yTcQvG99fevX4i8buMTolUVVnjWQye+mew4K6Ki3pHrTgSAai/Gev -HyICc/sgCq+dVEuhzf9gR7A/Xe8bVr2XIZYtCtFenTgCR2y59PYjJbigapordwj6 -xLEokCZYCDzifqrXPW+6MYgKBesntaFJ7qBFVHvmJ2WZICGoo7z7GJa7Um8M7YNR -TOlZ4iBgxcJlkoKM8xAfDoqXvneCbT+PHV28SSe9zE8P4c52hgQjxcCMElv924Sg -JPFI/2R80L5cFtHvma3AH/vLrrw4IgYmZNralw4/KBVEqE8AyvCazM90arQ+POuV -7LXTWtiBmelDGDfrs7vRWGJB82bSj6p4lVQgw1oudCvV0b4YacCs1aTPObpRhANl -6WLAYv7YTVWW4tAR+kg0Eeye7QUd5MjWHYbL ------END CERTIFICATE----- - -# Issuer: CN=GTS Root R3 O=Google Trust Services LLC -# Subject: CN=GTS Root R3 O=Google Trust Services LLC -# Label: "GTS Root R3" -# Serial: 159662495401136852707857743206 -# MD5 Fingerprint: 3e:e7:9d:58:02:94:46:51:94:e5:e0:22:4a:8b:e7:73 -# SHA1 Fingerprint: ed:e5:71:80:2b:c8:92:b9:5b:83:3c:d2:32:68:3f:09:cd:a0:1e:46 -# SHA256 Fingerprint: 34:d8:a7:3e:e2:08:d9:bc:db:0d:95:65:20:93:4b:4e:40:e6:94:82:59:6e:8b:6f:73:c8:42:6b:01:0a:6f:48 ------BEGIN CERTIFICATE----- -MIICCTCCAY6gAwIBAgINAgPluILrIPglJ209ZjAKBggqhkjOPQQDAzBHMQswCQYD -VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG -A1UEAxMLR1RTIFJvb3QgUjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw -WjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz -IExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjOPQIBBgUrgQQAIgNi -AAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout736G -jOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2ADDL2 -4CejQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW -BBTB8Sa6oC2uhYHP0/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEA9uEglRR7 -VKOQFhG/hMjqb2sXnh5GmCCbn9MN2azTL818+FsuVbu/3ZL3pAzcMeGiAjEA/Jdm -ZuVDFhOD3cffL74UOO0BzrEXGhF16b0DjyZ+hOXJYKaV11RZt+cRLInUue4X ------END CERTIFICATE----- - -# Issuer: CN=GTS Root R4 O=Google Trust Services LLC -# Subject: CN=GTS Root R4 O=Google Trust Services LLC -# Label: "GTS Root R4" -# Serial: 159662532700760215368942768210 -# MD5 Fingerprint: 43:96:83:77:19:4d:76:b3:9d:65:52:e4:1d:22:a5:e8 -# SHA1 Fingerprint: 77:d3:03:67:b5:e0:0c:15:f6:0c:38:61:df:7c:e1:3b:92:46:4d:47 -# SHA256 Fingerprint: 34:9d:fa:40:58:c5:e2:63:12:3b:39:8a:e7:95:57:3c:4e:13:13:c8:3f:e6:8f:93:55:6c:d5:e8:03:1b:3c:7d ------BEGIN CERTIFICATE----- -MIICCTCCAY6gAwIBAgINAgPlwGjvYxqccpBQUjAKBggqhkjOPQQDAzBHMQswCQYD -VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG -A1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw -WjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz -IExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjOPQIBBgUrgQQAIgNi -AATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzuhXyi -QHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvR -HYqjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW -BBSATNbrdP9JNqPV2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNpADBmAjEA6ED/g94D -9J+uHXqnLrmvT/aDHQ4thQEd0dlq7A/Cr8deVl5c1RxYIigL9zC2L7F8AjEA8GE8 -p/SgguMh1YQdc4acLa/KNJvxn7kjNuK8YAOdgLOaVsjh4rsUecrNIdSUtUlD ------END CERTIFICATE----- - -# Issuer: CN=Telia Root CA v2 O=Telia Finland Oyj -# Subject: CN=Telia Root CA v2 O=Telia Finland Oyj -# Label: "Telia Root CA v2" -# Serial: 7288924052977061235122729490515358 -# MD5 Fingerprint: 0e:8f:ac:aa:82:df:85:b1:f4:dc:10:1c:fc:99:d9:48 -# SHA1 Fingerprint: b9:99:cd:d1:73:50:8a:c4:47:05:08:9c:8c:88:fb:be:a0:2b:40:cd -# SHA256 Fingerprint: 24:2b:69:74:2f:cb:1e:5b:2a:bf:98:89:8b:94:57:21:87:54:4e:5b:4d:99:11:78:65:73:62:1f:6a:74:b8:2c ------BEGIN CERTIFICATE----- -MIIFdDCCA1ygAwIBAgIPAWdfJ9b+euPkrL4JWwWeMA0GCSqGSIb3DQEBCwUAMEQx -CzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UE -AwwQVGVsaWEgUm9vdCBDQSB2MjAeFw0xODExMjkxMTU1NTRaFw00MzExMjkxMTU1 -NTRaMEQxCzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZ -MBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2MjCCAiIwDQYJKoZIhvcNAQEBBQADggIP -ADCCAgoCggIBALLQPwe84nvQa5n44ndp586dpAO8gm2h/oFlH0wnrI4AuhZ76zBq -AMCzdGh+sq/H1WKzej9Qyow2RCRj0jbpDIX2Q3bVTKFgcmfiKDOlyzG4OiIjNLh9 -vVYiQJ3q9HsDrWj8soFPmNB06o3lfc1jw6P23pLCWBnglrvFxKk9pXSW/q/5iaq9 -lRdU2HhE8Qx3FZLgmEKnpNaqIJLNwaCzlrI6hEKNfdWV5Nbb6WLEWLN5xYzTNTOD -n3WhUidhOPFZPY5Q4L15POdslv5e2QJltI5c0BE0312/UqeBAMN/mUWZFdUXyApT -7GPzmX3MaRKGwhfwAZ6/hLzRUssbkmbOpFPlob/E2wnW5olWK8jjfN7j/4nlNW4o -6GwLI1GpJQXrSPjdscr6bAhR77cYbETKJuFzxokGgeWKrLDiKca5JLNrRBH0pUPC -TEPlcDaMtjNXepUugqD0XBCzYYP2AgWGLnwtbNwDRm41k9V6lS/eINhbfpSQBGq6 -WT0EBXWdN6IOLj3rwaRSg/7Qa9RmjtzG6RJOHSpXqhC8fF6CfaamyfItufUXJ63R -DolUK5X6wK0dmBR4M0KGCqlztft0DbcbMBnEWg4cJ7faGND/isgFuvGqHKI3t+ZI -pEYslOqodmJHixBTB0hXbOKSTbauBcvcwUpej6w9GU7C7WB1K9vBykLVAgMBAAGj -YzBhMB8GA1UdIwQYMBaAFHKs5DN5qkWH9v2sHZ7Wxy+G2CQ5MB0GA1UdDgQWBBRy -rOQzeapFh/b9rB2e1scvhtgkOTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw -AwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAoDtZpwmUPjaE0n4vOaWWl/oRrfxn83EJ -8rKJhGdEr7nv7ZbsnGTbMjBvZ5qsfl+yqwE2foH65IRe0qw24GtixX1LDoJt0nZi -0f6X+J8wfBj5tFJ3gh1229MdqfDBmgC9bXXYfef6xzijnHDoRnkDry5023X4blMM -A8iZGok1GTzTyVR8qPAs5m4HeW9q4ebqkYJpCh3DflminmtGFZhb069GHWLIzoBS -SRE/yQQSwxN8PzuKlts8oB4KtItUsiRnDe+Cy748fdHif64W1lZYudogsYMVoe+K -TTJvQS8TUoKU1xrBeKJR3Stwbbca+few4GeXVtt8YVMJAygCQMez2P2ccGrGKMOF -6eLtGpOg3kuYooQ+BXcBlj37tCAPnHICehIv1aO6UXivKitEZU61/Qrowc15h2Er -3oBXRb9n8ZuRXqWk7FlIEA04x7D6w0RtBPV4UBySllva9bguulvP5fBqnUsvWHMt -Ty3EHD70sz+rFQ47GUGKpMFXEmZxTPpT41frYpUJnlTd0cI8Vzy9OK2YZLe4A5pT -VmBds9hCG1xLEooc6+t9xnppxyd/pPiL8uSUZodL6ZQHCRJ5irLrdATczvREWeAW -ysUsWNc8e89ihmpQfTU2Zqf7N+cox9jQraVplI/owd8k+BsHMYeB2F326CjYSlKA -rBPuUBQemMc= ------END CERTIFICATE----- - -# Issuer: CN=D-TRUST BR Root CA 1 2020 O=D-Trust GmbH -# Subject: CN=D-TRUST BR Root CA 1 2020 O=D-Trust GmbH -# Label: "D-TRUST BR Root CA 1 2020" -# Serial: 165870826978392376648679885835942448534 -# MD5 Fingerprint: b5:aa:4b:d5:ed:f7:e3:55:2e:8f:72:0a:f3:75:b8:ed -# SHA1 Fingerprint: 1f:5b:98:f0:e3:b5:f7:74:3c:ed:e6:b0:36:7d:32:cd:f4:09:41:67 -# SHA256 Fingerprint: e5:9a:aa:81:60:09:c2:2b:ff:5b:25:ba:d3:7d:f3:06:f0:49:79:7c:1f:81:d8:5a:b0:89:e6:57:bd:8f:00:44 ------BEGIN CERTIFICATE----- -MIIC2zCCAmCgAwIBAgIQfMmPK4TX3+oPyWWa00tNljAKBggqhkjOPQQDAzBIMQsw -CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS -VVNUIEJSIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTA5NDUwMFoXDTM1MDIxMTA5 -NDQ1OVowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAG -A1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDEgMjAyMDB2MBAGByqGSM49AgEGBSuB -BAAiA2IABMbLxyjR+4T1mu9CFCDhQ2tuda38KwOE1HaTJddZO0Flax7mNCq7dPYS -zuht56vkPE4/RAiLzRZxy7+SmfSk1zxQVFKQhYN4lGdnoxwJGT11NIXe7WB9xwy0 -QVK5buXuQqOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHOREKv/ -VbNafAkl1bK6CKBrqx9tMA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6g -PKA6hjhodHRwOi8vY3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X2JyX3Jvb3Rf -Y2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVjdG9yeS5kLXRydXN0Lm5l -dC9DTj1ELVRSVVNUJTIwQlIlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxPPUQtVHJ1 -c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjO -PQQDAwNpADBmAjEAlJAtE/rhY/hhY+ithXhUkZy4kzg+GkHaQBZTQgjKL47xPoFW -wKrY7RjEsK70PvomAjEA8yjixtsrmfu3Ubgko6SUeho/5jbiA1czijDLgsfWFBHV -dWNbFJWcHwHP2NVypw87 ------END CERTIFICATE----- - -# Issuer: CN=D-TRUST EV Root CA 1 2020 O=D-Trust GmbH -# Subject: CN=D-TRUST EV Root CA 1 2020 O=D-Trust GmbH -# Label: "D-TRUST EV Root CA 1 2020" -# Serial: 126288379621884218666039612629459926992 -# MD5 Fingerprint: 8c:2d:9d:70:9f:48:99:11:06:11:fb:e9:cb:30:c0:6e -# SHA1 Fingerprint: 61:db:8c:21:59:69:03:90:d8:7c:9c:12:86:54:cf:9d:3d:f4:dd:07 -# SHA256 Fingerprint: 08:17:0d:1a:a3:64:53:90:1a:2f:95:92:45:e3:47:db:0c:8d:37:ab:aa:bc:56:b8:1a:a1:00:dc:95:89:70:db ------BEGIN CERTIFICATE----- -MIIC2zCCAmCgAwIBAgIQXwJB13qHfEwDo6yWjfv/0DAKBggqhkjOPQQDAzBIMQsw -CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS -VVNUIEVWIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTEwMDAwMFoXDTM1MDIxMTA5 -NTk1OVowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAG -A1UEAxMZRC1UUlVTVCBFViBSb290IENBIDEgMjAyMDB2MBAGByqGSM49AgEGBSuB -BAAiA2IABPEL3YZDIBnfl4XoIkqbz52Yv7QFJsnL46bSj8WeeHsxiamJrSc8ZRCC -/N/DnU7wMyPE0jL1HLDfMxddxfCxivnvubcUyilKwg+pf3VlSSowZ/Rk99Yad9rD -wpdhQntJraOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFH8QARY3 -OqQo5FD4pPfsazK2/umLMA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6g -PKA6hjhodHRwOi8vY3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X2V2X3Jvb3Rf -Y2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVjdG9yeS5kLXRydXN0Lm5l -dC9DTj1ELVRSVVNUJTIwRVYlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxPPUQtVHJ1 -c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjO -PQQDAwNpADBmAjEAyjzGKnXCXnViOTYAYFqLwZOZzNnbQTs7h5kXO9XMT8oi96CA -y/m0sRtW9XLS/BnRAjEAkfcwkz8QRitxpNA7RJvAKQIFskF3UfN5Wp6OFKBOQtJb -gfM0agPnIjhQW+0ZT0MW ------END CERTIFICATE----- - -# Issuer: CN=DigiCert TLS ECC P384 Root G5 O=DigiCert, Inc. -# Subject: CN=DigiCert TLS ECC P384 Root G5 O=DigiCert, Inc. -# Label: "DigiCert TLS ECC P384 Root G5" -# Serial: 13129116028163249804115411775095713523 -# MD5 Fingerprint: d3:71:04:6a:43:1c:db:a6:59:e1:a8:a3:aa:c5:71:ed -# SHA1 Fingerprint: 17:f3:de:5e:9f:0f:19:e9:8e:f6:1f:32:26:6e:20:c4:07:ae:30:ee -# SHA256 Fingerprint: 01:8e:13:f0:77:25:32:cf:80:9b:d1:b1:72:81:86:72:83:fc:48:c6:e1:3b:e9:c6:98:12:85:4a:49:0c:1b:05 ------BEGIN CERTIFICATE----- -MIICGTCCAZ+gAwIBAgIQCeCTZaz32ci5PhwLBCou8zAKBggqhkjOPQQDAzBOMQsw -CQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJjAkBgNVBAMTHURp -Z2lDZXJ0IFRMUyBFQ0MgUDM4NCBSb290IEc1MB4XDTIxMDExNTAwMDAwMFoXDTQ2 -MDExNDIzNTk1OVowTjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJ -bmMuMSYwJAYDVQQDEx1EaWdpQ2VydCBUTFMgRUNDIFAzODQgUm9vdCBHNTB2MBAG -ByqGSM49AgEGBSuBBAAiA2IABMFEoc8Rl1Ca3iOCNQfN0MsYndLxf3c1TzvdlHJS -7cI7+Oz6e2tYIOyZrsn8aLN1udsJ7MgT9U7GCh1mMEy7H0cKPGEQQil8pQgO4CLp -0zVozptjn4S1mU1YoI71VOeVyaNCMEAwHQYDVR0OBBYEFMFRRVBZqz7nLFr6ICIS -B4CIfBFqMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49 -BAMDA2gAMGUCMQCJao1H5+z8blUD2WdsJk6Dxv3J+ysTvLd6jLRl0mlpYxNjOyZQ -LgGheQaRnUi/wr4CMEfDFXuxoJGZSZOoPHzoRgaLLPIxAJSdYsiJvRmEFOml+wG4 -DXZDjC5Ty3zfDBeWUA== ------END CERTIFICATE----- - -# Issuer: CN=DigiCert TLS RSA4096 Root G5 O=DigiCert, Inc. -# Subject: CN=DigiCert TLS RSA4096 Root G5 O=DigiCert, Inc. -# Label: "DigiCert TLS RSA4096 Root G5" -# Serial: 11930366277458970227240571539258396554 -# MD5 Fingerprint: ac:fe:f7:34:96:a9:f2:b3:b4:12:4b:e4:27:41:6f:e1 -# SHA1 Fingerprint: a7:88:49:dc:5d:7c:75:8c:8c:de:39:98:56:b3:aa:d0:b2:a5:71:35 -# SHA256 Fingerprint: 37:1a:00:dc:05:33:b3:72:1a:7e:eb:40:e8:41:9e:70:79:9d:2b:0a:0f:2c:1d:80:69:31:65:f7:ce:c4:ad:75 ------BEGIN CERTIFICATE----- -MIIFZjCCA06gAwIBAgIQCPm0eKj6ftpqMzeJ3nzPijANBgkqhkiG9w0BAQwFADBN -MQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMT -HERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwHhcNMjEwMTE1MDAwMDAwWhcN -NDYwMTE0MjM1OTU5WjBNMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQs -IEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwggIi -MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz0PTJeRGd/fxmgefM1eS87IE+ -ajWOLrfn3q/5B03PMJ3qCQuZvWxX2hhKuHisOjmopkisLnLlvevxGs3npAOpPxG0 -2C+JFvuUAT27L/gTBaF4HI4o4EXgg/RZG5Wzrn4DReW+wkL+7vI8toUTmDKdFqgp -wgscONyfMXdcvyej/Cestyu9dJsXLfKB2l2w4SMXPohKEiPQ6s+d3gMXsUJKoBZM -pG2T6T867jp8nVid9E6P/DsjyG244gXazOvswzH016cpVIDPRFtMbzCe88zdH5RD -nU1/cHAN1DrRN/BsnZvAFJNY781BOHW8EwOVfH/jXOnVDdXifBBiqmvwPXbzP6Po -sMH976pXTayGpxi0KcEsDr9kvimM2AItzVwv8n/vFfQMFawKsPHTDU9qTXeXAaDx -Zre3zu/O7Oyldcqs4+Fj97ihBMi8ez9dLRYiVu1ISf6nL3kwJZu6ay0/nTvEF+cd -Lvvyz6b84xQslpghjLSR6Rlgg/IwKwZzUNWYOwbpx4oMYIwo+FKbbuH2TbsGJJvX -KyY//SovcfXWJL5/MZ4PbeiPT02jP/816t9JXkGPhvnxd3lLG7SjXi/7RgLQZhNe -XoVPzthwiHvOAbWWl9fNff2C+MIkwcoBOU+NosEUQB+cZtUMCUbW8tDRSHZWOkPL -tgoRObqME2wGtZ7P6wIDAQABo0IwQDAdBgNVHQ4EFgQUUTMc7TZArxfTJc1paPKv -TiM+s0EwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcN -AQEMBQADggIBAGCmr1tfV9qJ20tQqcQjNSH/0GEwhJG3PxDPJY7Jv0Y02cEhJhxw -GXIeo8mH/qlDZJY6yFMECrZBu8RHANmfGBg7sg7zNOok992vIGCukihfNudd5N7H -PNtQOa27PShNlnx2xlv0wdsUpasZYgcYQF+Xkdycx6u1UQ3maVNVzDl92sURVXLF -O4uJ+DQtpBflF+aZfTCIITfNMBc9uPK8qHWgQ9w+iUuQrm0D4ByjoJYJu32jtyoQ -REtGBzRj7TG5BO6jm5qu5jF49OokYTurWGT/u4cnYiWB39yhL/btp/96j1EuMPik -AdKFOV8BmZZvWltwGUb+hmA+rYAQCd05JS9Yf7vSdPD3Rh9GOUrYU9DzLjtxpdRv -/PNn5AeP3SYZ4Y1b+qOTEZvpyDrDVWiakuFSdjjo4bq9+0/V77PnSIMx8IIh47a+ -p6tv75/fTM8BuGJqIz3nCU2AG3swpMPdB380vqQmsvZB6Akd4yCYqjdP//fx4ilw -MUc/dNAUFvohigLVigmUdy7yWSiLfFCSCmZ4OIN1xLVaqBHG5cGdZlXPU8Sv13WF -qUITVuwhd4GTWgzqltlJyqEI8pc7bZsEGCREjnwB8twl2F6GmrE52/WRMmrRpnCK -ovfepEWFJqgejF0pW8hL2JpqA15w8oVPbEtoL8pU9ozaMv7Da4M/OMZ+ ------END CERTIFICATE----- - -# Issuer: CN=Certainly Root R1 O=Certainly -# Subject: CN=Certainly Root R1 O=Certainly -# Label: "Certainly Root R1" -# Serial: 188833316161142517227353805653483829216 -# MD5 Fingerprint: 07:70:d4:3e:82:87:a0:fa:33:36:13:f4:fa:33:e7:12 -# SHA1 Fingerprint: a0:50:ee:0f:28:71:f4:27:b2:12:6d:6f:50:96:25:ba:cc:86:42:af -# SHA256 Fingerprint: 77:b8:2c:d8:64:4c:43:05:f7:ac:c5:cb:15:6b:45:67:50:04:03:3d:51:c6:0c:62:02:a8:e0:c3:34:67:d3:a0 ------BEGIN CERTIFICATE----- -MIIFRzCCAy+gAwIBAgIRAI4P+UuQcWhlM1T01EQ5t+AwDQYJKoZIhvcNAQELBQAw -PTELMAkGA1UEBhMCVVMxEjAQBgNVBAoTCUNlcnRhaW5seTEaMBgGA1UEAxMRQ2Vy -dGFpbmx5IFJvb3QgUjEwHhcNMjEwNDAxMDAwMDAwWhcNNDYwNDAxMDAwMDAwWjA9 -MQswCQYDVQQGEwJVUzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0 -YWlubHkgUm9vdCBSMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANA2 -1B/q3avk0bbm+yLA3RMNansiExyXPGhjZjKcA7WNpIGD2ngwEc/csiu+kr+O5MQT -vqRoTNoCaBZ0vrLdBORrKt03H2As2/X3oXyVtwxwhi7xOu9S98zTm/mLvg7fMbed -aFySpvXl8wo0tf97ouSHocavFwDvA5HtqRxOcT3Si2yJ9HiG5mpJoM610rCrm/b0 -1C7jcvk2xusVtyWMOvwlDbMicyF0yEqWYZL1LwsYpfSt4u5BvQF5+paMjRcCMLT5 -r3gajLQ2EBAHBXDQ9DGQilHFhiZ5shGIXsXwClTNSaa/ApzSRKft43jvRl5tcdF5 -cBxGX1HpyTfcX35pe0HfNEXgO4T0oYoKNp43zGJS4YkNKPl6I7ENPT2a/Z2B7yyQ -wHtETrtJ4A5KVpK8y7XdeReJkd5hiXSSqOMyhb5OhaRLWcsrxXiOcVTQAjeZjOVJ -6uBUcqQRBi8LjMFbvrWhsFNunLhgkR9Za/kt9JQKl7XsxXYDVBtlUrpMklZRNaBA -2CnbrlJ2Oy0wQJuK0EJWtLeIAaSHO1OWzaMWj/Nmqhexx2DgwUMFDO6bW2BvBlyH -Wyf5QBGenDPBt+U1VwV/J84XIIwc/PH72jEpSe31C4SnT8H2TsIonPru4K8H+zMR -eiFPCyEQtkA6qyI6BJyLm4SGcprSp6XEtHWRqSsjAgMBAAGjQjBAMA4GA1UdDwEB -/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTgqj8ljZ9EXME66C6u -d0yEPmcM9DANBgkqhkiG9w0BAQsFAAOCAgEAuVevuBLaV4OPaAszHQNTVfSVcOQr -PbA56/qJYv331hgELyE03fFo8NWWWt7CgKPBjcZq91l3rhVkz1t5BXdm6ozTaw3d -8VkswTOlMIAVRQdFGjEitpIAq5lNOo93r6kiyi9jyhXWx8bwPWz8HA2YEGGeEaIi -1wrykXprOQ4vMMM2SZ/g6Q8CRFA3lFV96p/2O7qUpUzpvD5RtOjKkjZUbVwlKNrd -rRT90+7iIgXr0PK3aBLXWopBGsaSpVo7Y0VPv+E6dyIvXL9G+VoDhRNCX8reU9di -taY1BMJH/5n9hN9czulegChB8n3nHpDYT3Y+gjwN/KUD+nsa2UUeYNrEjvn8K8l7 -lcUq/6qJ34IxD3L/DCfXCh5WAFAeDJDBlrXYFIW7pw0WwfgHJBu6haEaBQmAupVj -yTrsJZ9/nbqkRxWbRHDxakvWOF5D8xh+UG7pWijmZeZ3Gzr9Hb4DJqPb1OG7fpYn -Kx3upPvaJVQTA945xsMfTZDsjxtK0hzthZU4UHlG1sGQUDGpXJpuHfUzVounmdLy -yCwzk5Iwx06MZTMQZBf9JBeW0Y3COmor6xOLRPIh80oat3df1+2IpHLlOR+Vnb5n -wXARPbv0+Em34yaXOp/SX3z7wJl8OSngex2/DaeP0ik0biQVy96QXr8axGbqwua6 -OV+KmalBWQewLK8= ------END CERTIFICATE----- - -# Issuer: CN=Certainly Root E1 O=Certainly -# Subject: CN=Certainly Root E1 O=Certainly -# Label: "Certainly Root E1" -# Serial: 8168531406727139161245376702891150584 -# MD5 Fingerprint: 0a:9e:ca:cd:3e:52:50:c6:36:f3:4b:a3:ed:a7:53:e9 -# SHA1 Fingerprint: f9:e1:6d:dc:01:89:cf:d5:82:45:63:3e:c5:37:7d:c2:eb:93:6f:2b -# SHA256 Fingerprint: b4:58:5f:22:e4:ac:75:6a:4e:86:12:a1:36:1c:5d:9d:03:1a:93:fd:84:fe:bb:77:8f:a3:06:8b:0f:c4:2d:c2 ------BEGIN CERTIFICATE----- -MIIB9zCCAX2gAwIBAgIQBiUzsUcDMydc+Y2aub/M+DAKBggqhkjOPQQDAzA9MQsw -CQYDVQQGEwJVUzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0YWlu -bHkgUm9vdCBFMTAeFw0yMTA0MDEwMDAwMDBaFw00NjA0MDEwMDAwMDBaMD0xCzAJ -BgNVBAYTAlVTMRIwEAYDVQQKEwlDZXJ0YWlubHkxGjAYBgNVBAMTEUNlcnRhaW5s -eSBSb290IEUxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3m/4fxzf7flHh4axpMCK -+IKXgOqPyEpeKn2IaKcBYhSRJHpcnqMXfYqGITQYUBsQ3tA3SybHGWCA6TS9YBk2 -QNYphwk8kXr2vBMj3VlOBF7PyAIcGFPBMdjaIOlEjeR2o0IwQDAOBgNVHQ8BAf8E -BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ygYy2R17ikq6+2uI1g4 -hevIIgcwCgYIKoZIzj0EAwMDaAAwZQIxALGOWiDDshliTd6wT99u0nCK8Z9+aozm -ut6Dacpps6kFtZaSF4fC0urQe87YQVt8rgIwRt7qy12a7DLCZRawTDBcMPPaTnOG -BtjOiQRINzf43TNRnXCve1XYAS59BWQOhriR ------END CERTIFICATE----- - -# Issuer: CN=Security Communication ECC RootCA1 O=SECOM Trust Systems CO.,LTD. -# Subject: CN=Security Communication ECC RootCA1 O=SECOM Trust Systems CO.,LTD. -# Label: "Security Communication ECC RootCA1" -# Serial: 15446673492073852651 -# MD5 Fingerprint: 7e:43:b0:92:68:ec:05:43:4c:98:ab:5d:35:2e:7e:86 -# SHA1 Fingerprint: b8:0e:26:a9:bf:d2:b2:3b:c0:ef:46:c9:ba:c7:bb:f6:1d:0d:41:41 -# SHA256 Fingerprint: e7:4f:bd:a5:5b:d5:64:c4:73:a3:6b:44:1a:a7:99:c8:a6:8e:07:74:40:e8:28:8b:9f:a1:e5:0e:4b:ba:ca:11 ------BEGIN CERTIFICATE----- -MIICODCCAb6gAwIBAgIJANZdm7N4gS7rMAoGCCqGSM49BAMDMGExCzAJBgNVBAYT -AkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMSswKQYD -VQQDEyJTZWN1cml0eSBDb21tdW5pY2F0aW9uIEVDQyBSb290Q0ExMB4XDTE2MDYx -NjA1MTUyOFoXDTM4MDExODA1MTUyOFowYTELMAkGA1UEBhMCSlAxJTAjBgNVBAoT -HFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKzApBgNVBAMTIlNlY3VyaXR5 -IENvbW11bmljYXRpb24gRUNDIFJvb3RDQTEwdjAQBgcqhkjOPQIBBgUrgQQAIgNi -AASkpW9gAwPDvTH00xecK4R1rOX9PVdu12O/5gSJko6BnOPpR27KkBLIE+Cnnfdl -dB9sELLo5OnvbYUymUSxXv3MdhDYW72ixvnWQuRXdtyQwjWpS4g8EkdtXP9JTxpK -ULGjQjBAMB0GA1UdDgQWBBSGHOf+LaVKiwj+KBH6vqNm+GBZLzAOBgNVHQ8BAf8E -BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjAVXUI9/Lbu -9zuxNuie9sRGKEkz0FhDKmMpzE2xtHqiuQ04pV1IKv3LsnNdo4gIxwwCMQDAqy0O -be0YottT6SXbVQjgUMzfRGEWgqtJsLKB7HOHeLRMsmIbEvoWTSVLY70eN9k= ------END CERTIFICATE----- - -# Issuer: CN=BJCA Global Root CA1 O=BEIJING CERTIFICATE AUTHORITY -# Subject: CN=BJCA Global Root CA1 O=BEIJING CERTIFICATE AUTHORITY -# Label: "BJCA Global Root CA1" -# Serial: 113562791157148395269083148143378328608 -# MD5 Fingerprint: 42:32:99:76:43:33:36:24:35:07:82:9b:28:f9:d0:90 -# SHA1 Fingerprint: d5:ec:8d:7b:4c:ba:79:f4:e7:e8:cb:9d:6b:ae:77:83:10:03:21:6a -# SHA256 Fingerprint: f3:89:6f:88:fe:7c:0a:88:27:66:a7:fa:6a:d2:74:9f:b5:7a:7f:3e:98:fb:76:9c:1f:a7:b0:9c:2c:44:d5:ae ------BEGIN CERTIFICATE----- -MIIFdDCCA1ygAwIBAgIQVW9l47TZkGobCdFsPsBsIDANBgkqhkiG9w0BAQsFADBU -MQswCQYDVQQGEwJDTjEmMCQGA1UECgwdQkVJSklORyBDRVJUSUZJQ0FURSBBVVRI -T1JJVFkxHTAbBgNVBAMMFEJKQ0EgR2xvYmFsIFJvb3QgQ0ExMB4XDTE5MTIxOTAz -MTYxN1oXDTQ0MTIxMjAzMTYxN1owVDELMAkGA1UEBhMCQ04xJjAkBgNVBAoMHUJF -SUpJTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZMR0wGwYDVQQDDBRCSkNBIEdsb2Jh -bCBSb290IENBMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAPFmCL3Z -xRVhy4QEQaVpN3cdwbB7+sN3SJATcmTRuHyQNZ0YeYjjlwE8R4HyDqKYDZ4/N+AZ -spDyRhySsTphzvq3Rp4Dhtczbu33RYx2N95ulpH3134rhxfVizXuhJFyV9xgw8O5 -58dnJCNPYwpj9mZ9S1WnP3hkSWkSl+BMDdMJoDIwOvqfwPKcxRIqLhy1BDPapDgR -at7GGPZHOiJBhyL8xIkoVNiMpTAK+BcWyqw3/XmnkRd4OJmtWO2y3syJfQOcs4ll -5+M7sSKGjwZteAf9kRJ/sGsciQ35uMt0WwfCyPQ10WRjeulumijWML3mG90Vr4Tq -nMfK9Q7q8l0ph49pczm+LiRvRSGsxdRpJQaDrXpIhRMsDQa4bHlW/KNnMoH1V6XK -V0Jp6VwkYe/iMBhORJhVb3rCk9gZtt58R4oRTklH2yiUAguUSiz5EtBP6DF+bHq/ -pj+bOT0CFqMYs2esWz8sgytnOYFcuX6U1WTdno9uruh8W7TXakdI136z1C2OVnZO -z2nxbkRs1CTqjSShGL+9V/6pmTW12xB3uD1IutbB5/EjPtffhZ0nPNRAvQoMvfXn -jSXWgXSHRtQpdaJCbPdzied9v3pKH9MiyRVVz99vfFXQpIsHETdfg6YmV6YBW37+ -WGgHqel62bno/1Afq8K0wM7o6v0PvY1NuLxxAgMBAAGjQjBAMB0GA1UdDgQWBBTF -7+3M2I0hxkjk49cULqcWk+WYATAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE -AwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAUoKsITQfI/Ki2Pm4rzc2IInRNwPWaZ+4 -YRC6ojGYWUfo0Q0lHhVBDOAqVdVXUsv45Mdpox1NcQJeXyFFYEhcCY5JEMEE3Kli -awLwQ8hOnThJdMkycFRtwUf8jrQ2ntScvd0g1lPJGKm1Vrl2i5VnZu69mP6u775u -+2D2/VnGKhs/I0qUJDAnyIm860Qkmss9vk/Ves6OF8tiwdneHg56/0OGNFK8YT88 -X7vZdrRTvJez/opMEi4r89fO4aL/3Xtw+zuhTaRjAv04l5U/BXCga99igUOLtFkN -SoxUnMW7gZ/NfaXvCyUeOiDbHPwfmGcCCtRzRBPbUYQaVQNW4AB+dAb/OMRyHdOo -P2gxXdMJxy6MW2Pg6Nwe0uxhHvLe5e/2mXZgLR6UcnHGCyoyx5JO1UbXHfmpGQrI -+pXObSOYqgs4rZpWDW+N8TEAiMEXnM0ZNjX+VVOg4DwzX5Ze4jLp3zO7Bkqp2IRz -znfSxqxx4VyjHQy7Ct9f4qNx2No3WqB4K/TUfet27fJhcKVlmtOJNBir+3I+17Q9 -eVzYH6Eze9mCUAyTF6ps3MKCuwJXNq+YJyo5UOGwifUll35HaBC07HPKs5fRJNz2 -YqAo07WjuGS3iGJCz51TzZm+ZGiPTx4SSPfSKcOYKMryMguTjClPPGAyzQWWYezy -r/6zcCwupvI= ------END CERTIFICATE----- - -# Issuer: CN=BJCA Global Root CA2 O=BEIJING CERTIFICATE AUTHORITY -# Subject: CN=BJCA Global Root CA2 O=BEIJING CERTIFICATE AUTHORITY -# Label: "BJCA Global Root CA2" -# Serial: 58605626836079930195615843123109055211 -# MD5 Fingerprint: 5e:0a:f6:47:5f:a6:14:e8:11:01:95:3f:4d:01:eb:3c -# SHA1 Fingerprint: f4:27:86:eb:6e:b8:6d:88:31:67:02:fb:ba:66:a4:53:00:aa:7a:a6 -# SHA256 Fingerprint: 57:4d:f6:93:1e:27:80:39:66:7b:72:0a:fd:c1:60:0f:c2:7e:b6:6d:d3:09:29:79:fb:73:85:64:87:21:28:82 ------BEGIN CERTIFICATE----- -MIICJTCCAaugAwIBAgIQLBcIfWQqwP6FGFkGz7RK6zAKBggqhkjOPQQDAzBUMQsw -CQYDVQQGEwJDTjEmMCQGA1UECgwdQkVJSklORyBDRVJUSUZJQ0FURSBBVVRIT1JJ -VFkxHTAbBgNVBAMMFEJKQ0EgR2xvYmFsIFJvb3QgQ0EyMB4XDTE5MTIxOTAzMTgy -MVoXDTQ0MTIxMjAzMTgyMVowVDELMAkGA1UEBhMCQ04xJjAkBgNVBAoMHUJFSUpJ -TkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZMR0wGwYDVQQDDBRCSkNBIEdsb2JhbCBS -b290IENBMjB2MBAGByqGSM49AgEGBSuBBAAiA2IABJ3LgJGNU2e1uVCxA/jlSR9B -IgmwUVJY1is0j8USRhTFiy8shP8sbqjV8QnjAyEUxEM9fMEsxEtqSs3ph+B99iK+ -+kpRuDCK/eHeGBIK9ke35xe/J4rUQUyWPGCWwf0VHKNCMEAwHQYDVR0OBBYEFNJK -sVF/BvDRgh9Obl+rg/xI1LCRMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD -AgEGMAoGCCqGSM49BAMDA2gAMGUCMBq8W9f+qdJUDkpd0m2xQNz0Q9XSSpkZElaA -94M04TVOSG0ED1cxMDAtsaqdAzjbBgIxAMvMh1PLet8gUXOQwKhbYdDFUDn9hf7B -43j4ptZLvZuHjw/l1lOWqzzIQNph91Oj9w== ------END CERTIFICATE----- - -# Issuer: CN=Sectigo Public Server Authentication Root E46 O=Sectigo Limited -# Subject: CN=Sectigo Public Server Authentication Root E46 O=Sectigo Limited -# Label: "Sectigo Public Server Authentication Root E46" -# Serial: 88989738453351742415770396670917916916 -# MD5 Fingerprint: 28:23:f8:b2:98:5c:37:16:3b:3e:46:13:4e:b0:b3:01 -# SHA1 Fingerprint: ec:8a:39:6c:40:f0:2e:bc:42:75:d4:9f:ab:1c:1a:5b:67:be:d2:9a -# SHA256 Fingerprint: c9:0f:26:f0:fb:1b:40:18:b2:22:27:51:9b:5c:a2:b5:3e:2c:a5:b3:be:5c:f1:8e:fe:1b:ef:47:38:0c:53:83 ------BEGIN CERTIFICATE----- -MIICOjCCAcGgAwIBAgIQQvLM2htpN0RfFf51KBC49DAKBggqhkjOPQQDAzBfMQsw -CQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1T -ZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwHhcN -MjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5WjBfMQswCQYDVQQGEwJHQjEYMBYG -A1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1YmxpYyBT -ZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUrgQQA -IgNiAAR2+pmpbiDt+dd34wc7qNs9Xzjoq1WmVk/WSOrsfy2qw7LFeeyZYX8QeccC -WvkEN/U0NSt3zn8gj1KjAIns1aeibVvjS5KToID1AZTc8GgHHs3u/iVStSBDHBv+ -6xnOQ6OjQjBAMB0GA1UdDgQWBBTRItpMWfFLXyY4qp3W7usNw/upYTAOBgNVHQ8B -Af8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNnADBkAjAn7qRa -qCG76UeXlImldCBteU/IvZNeWBj7LRoAasm4PdCkT0RHlAFWovgzJQxC36oCMB3q -4S6ILuH5px0CMk7yn2xVdOOurvulGu7t0vzCAxHrRVxgED1cf5kDW21USAGKcw== ------END CERTIFICATE----- - -# Issuer: CN=Sectigo Public Server Authentication Root R46 O=Sectigo Limited -# Subject: CN=Sectigo Public Server Authentication Root R46 O=Sectigo Limited -# Label: "Sectigo Public Server Authentication Root R46" -# Serial: 156256931880233212765902055439220583700 -# MD5 Fingerprint: 32:10:09:52:00:d5:7e:6c:43:df:15:c0:b1:16:93:e5 -# SHA1 Fingerprint: ad:98:f9:f3:e4:7d:75:3b:65:d4:82:b3:a4:52:17:bb:6e:f5:e4:38 -# SHA256 Fingerprint: 7b:b6:47:a6:2a:ee:ac:88:bf:25:7a:a5:22:d0:1f:fe:a3:95:e0:ab:45:c7:3f:93:f6:56:54:ec:38:f2:5a:06 ------BEGIN CERTIFICATE----- -MIIFijCCA3KgAwIBAgIQdY39i658BwD6qSWn4cetFDANBgkqhkiG9w0BAQwFADBf -MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQD -Ey1TZWN0aWdvIFB1YmxpYyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYw -HhcNMjEwMzIyMDAwMDAwWhcNNDYwMzIxMjM1OTU5WjBfMQswCQYDVQQGEwJHQjEY -MBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTYwNAYDVQQDEy1TZWN0aWdvIFB1Ymxp -YyBTZXJ2ZXIgQXV0aGVudGljYXRpb24gUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEB -AQUAA4ICDwAwggIKAoICAQCTvtU2UnXYASOgHEdCSe5jtrch/cSV1UgrJnwUUxDa -ef0rty2k1Cz66jLdScK5vQ9IPXtamFSvnl0xdE8H/FAh3aTPaE8bEmNtJZlMKpnz -SDBh+oF8HqcIStw+KxwfGExxqjWMrfhu6DtK2eWUAtaJhBOqbchPM8xQljeSM9xf -iOefVNlI8JhD1mb9nxc4Q8UBUQvX4yMPFF1bFOdLvt30yNoDN9HWOaEhUTCDsG3X -ME6WW5HwcCSrv0WBZEMNvSE6Lzzpng3LILVCJ8zab5vuZDCQOc2TZYEhMbUjUDM3 -IuM47fgxMMxF/mL50V0yeUKH32rMVhlATc6qu/m1dkmU8Sf4kaWD5QazYw6A3OAS -VYCmO2a0OYctyPDQ0RTp5A1NDvZdV3LFOxxHVp3i1fuBYYzMTYCQNFu31xR13NgE -SJ/AwSiItOkcyqex8Va3e0lMWeUgFaiEAin6OJRpmkkGj80feRQXEgyDet4fsZfu -+Zd4KKTIRJLpfSYFplhym3kT2BFfrsU4YjRosoYwjviQYZ4ybPUHNs2iTG7sijbt -8uaZFURww3y8nDnAtOFr94MlI1fZEoDlSfB1D++N6xybVCi0ITz8fAr/73trdf+L -HaAZBav6+CuBQug4urv7qv094PPK306Xlynt8xhW6aWWrL3DkJiy4Pmi1KZHQ3xt -zwIDAQABo0IwQDAdBgNVHQ4EFgQUVnNYZJX5khqwEioEYnmhQBWIIUkwDgYDVR0P -AQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAC9c -mTz8Bl6MlC5w6tIyMY208FHVvArzZJ8HXtXBc2hkeqK5Duj5XYUtqDdFqij0lgVQ -YKlJfp/imTYpE0RHap1VIDzYm/EDMrraQKFz6oOht0SmDpkBm+S8f74TlH7Kph52 -gDY9hAaLMyZlbcp+nv4fjFg4exqDsQ+8FxG75gbMY/qB8oFM2gsQa6H61SilzwZA -Fv97fRheORKkU55+MkIQpiGRqRxOF3yEvJ+M0ejf5lG5Nkc/kLnHvALcWxxPDkjB -JYOcCj+esQMzEhonrPcibCTRAUH4WAP+JWgiH5paPHxsnnVI84HxZmduTILA7rpX -DhjvLpr3Etiga+kFpaHpaPi8TD8SHkXoUsCjvxInebnMMTzD9joiFgOgyY9mpFui -TdaBJQbpdqQACj7LzTWb4OE4y2BThihCQRxEV+ioratF4yUQvNs+ZUH7G6aXD+u5 -dHn5HrwdVw1Hr8Mvn4dGp+smWg9WY7ViYG4A++MnESLn/pmPNPW56MORcr3Ywx65 -LvKRRFHQV80MNNVIIb/bE/FmJUNS0nAiNs2fxBx1IK1jcmMGDw4nztJqDby1ORrp -0XZ60Vzk50lJLVU3aPAaOpg+VBeHVOmmJ1CJeyAvP/+/oYtKR5j/K3tJPsMpRmAY -QqszKbrAKbkTidOIijlBO8n9pu0f9GBj39ItVQGL ------END CERTIFICATE----- - -# Issuer: CN=SSL.com TLS RSA Root CA 2022 O=SSL Corporation -# Subject: CN=SSL.com TLS RSA Root CA 2022 O=SSL Corporation -# Label: "SSL.com TLS RSA Root CA 2022" -# Serial: 148535279242832292258835760425842727825 -# MD5 Fingerprint: d8:4e:c6:59:30:d8:fe:a0:d6:7a:5a:2c:2c:69:78:da -# SHA1 Fingerprint: ec:2c:83:40:72:af:26:95:10:ff:0e:f2:03:ee:31:70:f6:78:9d:ca -# SHA256 Fingerprint: 8f:af:7d:2e:2c:b4:70:9b:b8:e0:b3:36:66:bf:75:a5:dd:45:b5:de:48:0f:8e:a8:d4:bf:e6:be:bc:17:f2:ed ------BEGIN CERTIFICATE----- -MIIFiTCCA3GgAwIBAgIQb77arXO9CEDii02+1PdbkTANBgkqhkiG9w0BAQsFADBO -MQswCQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQD -DBxTU0wuY29tIFRMUyBSU0EgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzQyMloX -DTQ2MDgxOTE2MzQyMVowTjELMAkGA1UEBhMCVVMxGDAWBgNVBAoMD1NTTCBDb3Jw -b3JhdGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgUlNBIFJvb3QgQ0EgMjAyMjCC -AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANCkCXJPQIgSYT41I57u9nTP -L3tYPc48DRAokC+X94xI2KDYJbFMsBFMF3NQ0CJKY7uB0ylu1bUJPiYYf7ISf5OY -t6/wNr/y7hienDtSxUcZXXTzZGbVXcdotL8bHAajvI9AI7YexoS9UcQbOcGV0ins -S657Lb85/bRi3pZ7QcacoOAGcvvwB5cJOYF0r/c0WRFXCsJbwST0MXMwgsadugL3 -PnxEX4MN8/HdIGkWCVDi1FW24IBydm5MR7d1VVm0U3TZlMZBrViKMWYPHqIbKUBO -L9975hYsLfy/7PO0+r4Y9ptJ1O4Fbtk085zx7AGL0SDGD6C1vBdOSHtRwvzpXGk3 -R2azaPgVKPC506QVzFpPulJwoxJF3ca6TvvC0PeoUidtbnm1jPx7jMEWTO6Af77w -dr5BUxIzrlo4QqvXDz5BjXYHMtWrifZOZ9mxQnUjbvPNQrL8VfVThxc7wDNY8VLS -+YCk8OjwO4s4zKTGkH8PnP2L0aPP2oOnaclQNtVcBdIKQXTbYxE3waWglksejBYS -d66UNHsef8JmAOSqg+qKkK3ONkRN0VHpvB/zagX9wHQfJRlAUW7qglFA35u5CCoG -AtUjHBPW6dvbxrB6y3snm/vg1UYk7RBLY0ulBY+6uB0rpvqR4pJSvezrZ5dtmi2f -gTIFZzL7SAg/2SW4BCUvAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0j -BBgwFoAU+y437uOEeicuzRk1sTN8/9REQrkwHQYDVR0OBBYEFPsuN+7jhHonLs0Z -NbEzfP/UREK5MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAjYlt -hEUY8U+zoO9opMAdrDC8Z2awms22qyIZZtM7QbUQnRC6cm4pJCAcAZli05bg4vsM -QtfhWsSWTVTNj8pDU/0quOr4ZcoBwq1gaAafORpR2eCNJvkLTqVTJXojpBzOCBvf -R4iyrT7gJ4eLSYwfqUdYe5byiB0YrrPRpgqU+tvT5TgKa3kSM/tKWTcWQA673vWJ -DPFs0/dRa1419dvAJuoSc06pkZCmF8NsLzjUo3KUQyxi4U5cMj29TH0ZR6LDSeeW -P4+a0zvkEdiLA9z2tmBVGKaBUfPhqBVq6+AL8BQx1rmMRTqoENjwuSfr98t67wVy -lrXEj5ZzxOhWc5y8aVFjvO9nHEMaX3cZHxj4HCUp+UmZKbaSPaKDN7EgkaibMOlq -bLQjk2UEqxHzDh1TJElTHaE/nUiSEeJ9DU/1172iWD54nR4fK/4huxoTtrEoZP2w -AgDHbICivRZQIA9ygV/MlP+7mea6kMvq+cYMwq7FGc4zoWtcu358NFcXrfA/rs3q -r5nsLFR+jM4uElZI7xc7P0peYNLcdDa8pUNjyw9bowJWCZ4kLOGGgYz+qxcs+sji -Mho6/4UIyYOf8kpIEFR3N+2ivEC+5BB09+Rbu7nzifmPQdjH5FCQNYA+HLhNkNPU -98OwoX6EyneSMSy4kLGCenROmxMmtNVQZlR4rmA= ------END CERTIFICATE----- - -# Issuer: CN=SSL.com TLS ECC Root CA 2022 O=SSL Corporation -# Subject: CN=SSL.com TLS ECC Root CA 2022 O=SSL Corporation -# Label: "SSL.com TLS ECC Root CA 2022" -# Serial: 26605119622390491762507526719404364228 -# MD5 Fingerprint: 99:d7:5c:f1:51:36:cc:e9:ce:d9:19:2e:77:71:56:c5 -# SHA1 Fingerprint: 9f:5f:d9:1a:54:6d:f5:0c:71:f0:ee:7a:bd:17:49:98:84:73:e2:39 -# SHA256 Fingerprint: c3:2f:fd:9f:46:f9:36:d1:6c:36:73:99:09:59:43:4b:9a:d6:0a:af:bb:9e:7c:f3:36:54:f1:44:cc:1b:a1:43 ------BEGIN CERTIFICATE----- -MIICOjCCAcCgAwIBAgIQFAP1q/s3ixdAW+JDsqXRxDAKBggqhkjOPQQDAzBOMQsw -CQYDVQQGEwJVUzEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMSUwIwYDVQQDDBxT -U0wuY29tIFRMUyBFQ0MgUm9vdCBDQSAyMDIyMB4XDTIyMDgyNTE2MzM0OFoXDTQ2 -MDgxOTE2MzM0N1owTjELMAkGA1UEBhMCVVMxGDAWBgNVBAoMD1NTTCBDb3Jwb3Jh -dGlvbjElMCMGA1UEAwwcU1NMLmNvbSBUTFMgRUNDIFJvb3QgQ0EgMjAyMjB2MBAG -ByqGSM49AgEGBSuBBAAiA2IABEUpNXP6wrgjzhR9qLFNoFs27iosU8NgCTWyJGYm -acCzldZdkkAZDsalE3D07xJRKF3nzL35PIXBz5SQySvOkkJYWWf9lCcQZIxPBLFN -SeR7T5v15wj4A4j3p8OSSxlUgaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSME -GDAWgBSJjy+j6CugFFR781a4Jl9nOAuc0DAdBgNVHQ4EFgQUiY8vo+groBRUe/NW -uCZfZzgLnNAwDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2gAMGUCMFXjIlbp -15IkWE8elDIPDAI2wv2sdDJO4fscgIijzPvX6yv/N33w7deedWo1dlJF4AIxAMeN -b0Igj762TVntd00pxCAgRWSGOlDGxK0tk/UYfXLtqc/ErFc2KAhl3zx5Zn6g6g== ------END CERTIFICATE----- - -# Issuer: CN=Atos TrustedRoot Root CA ECC TLS 2021 O=Atos -# Subject: CN=Atos TrustedRoot Root CA ECC TLS 2021 O=Atos -# Label: "Atos TrustedRoot Root CA ECC TLS 2021" -# Serial: 81873346711060652204712539181482831616 -# MD5 Fingerprint: 16:9f:ad:f1:70:ad:79:d6:ed:29:b4:d1:c5:79:70:a8 -# SHA1 Fingerprint: 9e:bc:75:10:42:b3:02:f3:81:f4:f7:30:62:d4:8f:c3:a7:51:b2:dd -# SHA256 Fingerprint: b2:fa:e5:3e:14:cc:d7:ab:92:12:06:47:01:ae:27:9c:1d:89:88:fa:cb:77:5f:a8:a0:08:91:4e:66:39:88:a8 ------BEGIN CERTIFICATE----- -MIICFTCCAZugAwIBAgIQPZg7pmY9kGP3fiZXOATvADAKBggqhkjOPQQDAzBMMS4w -LAYDVQQDDCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgRUNDIFRMUyAyMDIxMQ0w -CwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0yMTA0MjIwOTI2MjNaFw00MTA0 -MTcwOTI2MjJaMEwxLjAsBgNVBAMMJUF0b3MgVHJ1c3RlZFJvb3QgUm9vdCBDQSBF -Q0MgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYTAkRFMHYwEAYHKoZI -zj0CAQYFK4EEACIDYgAEloZYKDcKZ9Cg3iQZGeHkBQcfl+3oZIK59sRxUM6KDP/X -tXa7oWyTbIOiaG6l2b4siJVBzV3dscqDY4PMwL502eCdpO5KTlbgmClBk1IQ1SQ4 -AjJn8ZQSb+/Xxd4u/RmAo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBR2 -KCXWfeBmmnoJsmo7jjPXNtNPojAOBgNVHQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMD -aAAwZQIwW5kp85wxtolrbNa9d+F851F+uDrNozZffPc8dz7kUK2o59JZDCaOMDtu -CCrCp1rIAjEAmeMM56PDr9NJLkaCI2ZdyQAUEv049OGYa3cpetskz2VAv9LcjBHo -9H1/IISpQuQo ------END CERTIFICATE----- - -# Issuer: CN=Atos TrustedRoot Root CA RSA TLS 2021 O=Atos -# Subject: CN=Atos TrustedRoot Root CA RSA TLS 2021 O=Atos -# Label: "Atos TrustedRoot Root CA RSA TLS 2021" -# Serial: 111436099570196163832749341232207667876 -# MD5 Fingerprint: d4:d3:46:b8:9a:c0:9c:76:5d:9e:3a:c3:b9:99:31:d2 -# SHA1 Fingerprint: 18:52:3b:0d:06:37:e4:d6:3a:df:23:e4:98:fb:5b:16:fb:86:74:48 -# SHA256 Fingerprint: 81:a9:08:8e:a5:9f:b3:64:c5:48:a6:f8:55:59:09:9b:6f:04:05:ef:bf:18:e5:32:4e:c9:f4:57:ba:00:11:2f ------BEGIN CERTIFICATE----- -MIIFZDCCA0ygAwIBAgIQU9XP5hmTC/srBRLYwiqipDANBgkqhkiG9w0BAQwFADBM -MS4wLAYDVQQDDCVBdG9zIFRydXN0ZWRSb290IFJvb3QgQ0EgUlNBIFRMUyAyMDIx -MQ0wCwYDVQQKDARBdG9zMQswCQYDVQQGEwJERTAeFw0yMTA0MjIwOTIxMTBaFw00 -MTA0MTcwOTIxMDlaMEwxLjAsBgNVBAMMJUF0b3MgVHJ1c3RlZFJvb3QgUm9vdCBD -QSBSU0EgVExTIDIwMjExDTALBgNVBAoMBEF0b3MxCzAJBgNVBAYTAkRFMIICIjAN -BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtoAOxHm9BYx9sKOdTSJNy/BBl01Z -4NH+VoyX8te9j2y3I49f1cTYQcvyAh5x5en2XssIKl4w8i1mx4QbZFc4nXUtVsYv -Ye+W/CBGvevUez8/fEc4BKkbqlLfEzfTFRVOvV98r61jx3ncCHvVoOX3W3WsgFWZ -kmGbzSoXfduP9LVq6hdKZChmFSlsAvFr1bqjM9xaZ6cF4r9lthawEO3NUDPJcFDs -GY6wx/J0W2tExn2WuZgIWWbeKQGb9Cpt0xU6kGpn8bRrZtkh68rZYnxGEFzedUln -nkL5/nWpo63/dgpnQOPF943HhZpZnmKaau1Fh5hnstVKPNe0OwANwI8f4UDErmwh -3El+fsqyjW22v5MvoVw+j8rtgI5Y4dtXz4U2OLJxpAmMkokIiEjxQGMYsluMWuPD -0xeqqxmjLBvk1cbiZnrXghmmOxYsL3GHX0WelXOTwkKBIROW1527k2gV+p2kHYzy -geBYBr3JtuP2iV2J+axEoctr+hbxx1A9JNr3w+SH1VbxT5Aw+kUJWdo0zuATHAR8 -ANSbhqRAvNncTFd+rrcztl524WWLZt+NyteYr842mIycg5kDcPOvdO3GDjbnvezB -c6eUWsuSZIKmAMFwoW4sKeFYV+xafJlrJaSQOoD0IJ2azsct+bJLKZWD6TWNp0lI -pw9MGZHQ9b8Q4HECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU -dEmZ0f+0emhFdcN+tNzMzjkz2ggwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB -DAUAA4ICAQAjQ1MkYlxt/T7Cz1UAbMVWiLkO3TriJQ2VSpfKgInuKs1l+NsW4AmS -4BjHeJi78+xCUvuppILXTdiK/ORO/auQxDh1MoSf/7OwKwIzNsAQkG8dnK/haZPs -o0UvFJ/1TCplQ3IM98P4lYsU84UgYt1UU90s3BiVaU+DR3BAM1h3Egyi61IxHkzJ -qM7F78PRreBrAwA0JrRUITWXAdxfG/F851X6LWh3e9NpzNMOa7pNdkTWwhWaJuyw -xfW70Xp0wmzNxbVe9kzmWy2B27O3Opee7c9GslA9hGCZcbUztVdF5kJHdWoOsAgM -rr3e97sPWD2PAzHoPYJQyi9eDF20l74gNAf0xBLh7tew2VktafcxBPTy+av5EzH4 -AXcOPUIjJsyacmdRIXrMPIWo6iFqO9taPKU0nprALN+AnCng33eU0aKAQv9qTFsR -0PXNor6uzFFcw9VUewyu1rkGd4Di7wcaaMxZUa1+XGdrudviB0JbuAEFWDlN5LuY -o7Ey7Nmj1m+UI/87tyll5gfp77YZ6ufCOB0yiJA8EytuzO+rdwY0d4RPcuSBhPm5 -dDTedk+SKlOxJTnbPP/lPqYO5Wue/9vsL3SD3460s6neFE3/MaNFcyT6lSnMEpcE -oji2jbDwN/zIIX8/syQbPYtuzE2wFg2WHYMfRsCbvUOZ58SWLs5fyQ== ------END CERTIFICATE----- - -# Issuer: CN=TrustAsia Global Root CA G3 O=TrustAsia Technologies, Inc. -# Subject: CN=TrustAsia Global Root CA G3 O=TrustAsia Technologies, Inc. -# Label: "TrustAsia Global Root CA G3" -# Serial: 576386314500428537169965010905813481816650257167 -# MD5 Fingerprint: 30:42:1b:b7:bb:81:75:35:e4:16:4f:53:d2:94:de:04 -# SHA1 Fingerprint: 63:cf:b6:c1:27:2b:56:e4:88:8e:1c:23:9a:b6:2e:81:47:24:c3:c7 -# SHA256 Fingerprint: e0:d3:22:6a:eb:11:63:c2:e4:8f:f9:be:3b:50:b4:c6:43:1b:e7:bb:1e:ac:c5:c3:6b:5d:5e:c5:09:03:9a:08 ------BEGIN CERTIFICATE----- -MIIFpTCCA42gAwIBAgIUZPYOZXdhaqs7tOqFhLuxibhxkw8wDQYJKoZIhvcNAQEM -BQAwWjELMAkGA1UEBhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dp -ZXMsIEluYy4xJDAiBgNVBAMMG1RydXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHMzAe -Fw0yMTA1MjAwMjEwMTlaFw00NjA1MTkwMjEwMTlaMFoxCzAJBgNVBAYTAkNOMSUw -IwYDVQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSQwIgYDVQQDDBtU -cnVzdEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzMwggIiMA0GCSqGSIb3DQEBAQUAA4IC -DwAwggIKAoICAQDAMYJhkuSUGwoqZdC+BqmHO1ES6nBBruL7dOoKjbmzTNyPtxNS -T1QY4SxzlZHFZjtqz6xjbYdT8PfxObegQ2OwxANdV6nnRM7EoYNl9lA+sX4WuDqK -AtCWHwDNBSHvBm3dIZwZQ0WhxeiAysKtQGIXBsaqvPPW5vxQfmZCHzyLpnl5hkA1 -nyDvP+uLRx+PjsXUjrYsyUQE49RDdT/VP68czH5GX6zfZBCK70bwkPAPLfSIC7Ep -qq+FqklYqL9joDiR5rPmd2jE+SoZhLsO4fWvieylL1AgdB4SQXMeJNnKziyhWTXA -yB1GJ2Faj/lN03J5Zh6fFZAhLf3ti1ZwA0pJPn9pMRJpxx5cynoTi+jm9WAPzJMs -hH/x/Gr8m0ed262IPfN2dTPXS6TIi/n1Q1hPy8gDVI+lhXgEGvNz8teHHUGf59gX -zhqcD0r83ERoVGjiQTz+LISGNzzNPy+i2+f3VANfWdP3kXjHi3dqFuVJhZBFcnAv -kV34PmVACxmZySYgWmjBNb9Pp1Hx2BErW+Canig7CjoKH8GB5S7wprlppYiU5msT -f9FkPz2ccEblooV7WIQn3MSAPmeamseaMQ4w7OYXQJXZRe0Blqq/DPNL0WP3E1jA -uPP6Z92bfW1K/zJMtSU7/xxnD4UiWQWRkUF3gdCFTIcQcf+eQxuulXUtgQIDAQAB -o2MwYTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFEDk5PIj7zjKsK5Xf/Ih -MBY027ySMB0GA1UdDgQWBBRA5OTyI+84yrCuV3/yITAWNNu8kjAOBgNVHQ8BAf8E -BAMCAQYwDQYJKoZIhvcNAQEMBQADggIBACY7UeFNOPMyGLS0XuFlXsSUT9SnYaP4 -wM8zAQLpw6o1D/GUE3d3NZ4tVlFEbuHGLige/9rsR82XRBf34EzC4Xx8MnpmyFq2 -XFNFV1pF1AWZLy4jVe5jaN/TG3inEpQGAHUNcoTpLrxaatXeL1nHo+zSh2bbt1S1 -JKv0Q3jbSwTEb93mPmY+KfJLaHEih6D4sTNjduMNhXJEIlU/HHzp/LgV6FL6qj6j -ITk1dImmasI5+njPtqzn59ZW/yOSLlALqbUHM/Q4X6RJpstlcHboCoWASzY9M/eV -VHUl2qzEc4Jl6VL1XP04lQJqaTDFHApXB64ipCz5xUG3uOyfT0gA+QEEVcys+TIx -xHWVBqB/0Y0n3bOppHKH/lmLmnp0Ft0WpWIp6zqW3IunaFnT63eROfjXy9mPX1on -AX1daBli2MjN9LdyR75bl87yraKZk62Uy5P2EgmVtqvXO9A/EcswFi55gORngS1d -7XB4tmBZrOFdRWOPyN9yaFvqHbgB8X7754qz41SgOAngPN5C8sLtLpvzHzW2Ntjj -gKGLzZlkD8Kqq7HK9W+eQ42EVJmzbsASZthwEPEGNTNDqJwuuhQxzhB/HIbjj9LV -+Hfsm6vxL2PZQl/gZ4FkkfGXL/xuJvYz+NO1+MRiqzFRJQJ6+N1rZdVtTTDIZbpo -FGWsJwt0ivKH ------END CERTIFICATE----- - -# Issuer: CN=TrustAsia Global Root CA G4 O=TrustAsia Technologies, Inc. -# Subject: CN=TrustAsia Global Root CA G4 O=TrustAsia Technologies, Inc. -# Label: "TrustAsia Global Root CA G4" -# Serial: 451799571007117016466790293371524403291602933463 -# MD5 Fingerprint: 54:dd:b2:d7:5f:d8:3e:ed:7c:e0:0b:2e:cc:ed:eb:eb -# SHA1 Fingerprint: 57:73:a5:61:5d:80:b2:e6:ac:38:82:fc:68:07:31:ac:9f:b5:92:5a -# SHA256 Fingerprint: be:4b:56:cb:50:56:c0:13:6a:52:6d:f4:44:50:8d:aa:36:a0:b5:4f:42:e4:ac:38:f7:2a:f4:70:e4:79:65:4c ------BEGIN CERTIFICATE----- -MIICVTCCAdygAwIBAgIUTyNkuI6XY57GU4HBdk7LKnQV1tcwCgYIKoZIzj0EAwMw -WjELMAkGA1UEBhMCQ04xJTAjBgNVBAoMHFRydXN0QXNpYSBUZWNobm9sb2dpZXMs -IEluYy4xJDAiBgNVBAMMG1RydXN0QXNpYSBHbG9iYWwgUm9vdCBDQSBHNDAeFw0y -MTA1MjAwMjEwMjJaFw00NjA1MTkwMjEwMjJaMFoxCzAJBgNVBAYTAkNOMSUwIwYD -VQQKDBxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSQwIgYDVQQDDBtUcnVz -dEFzaWEgR2xvYmFsIFJvb3QgQ0EgRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATx -s8045CVD5d4ZCbuBeaIVXxVjAd7Cq92zphtnS4CDr5nLrBfbK5bKfFJV4hrhPVbw -LxYI+hW8m7tH5j/uqOFMjPXTNvk4XatwmkcN4oFBButJ+bAp3TPsUKV/eSm4IJij -YzBhMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUpbtKl86zK3+kMd6Xg1mD -pm9xy94wHQYDVR0OBBYEFKW7SpfOsyt/pDHel4NZg6ZvccveMA4GA1UdDwEB/wQE -AwIBBjAKBggqhkjOPQQDAwNnADBkAjBe8usGzEkxn0AAbbd+NvBNEU/zy4k6LHiR -UKNbwMp1JvK/kF0LgoxgKJ/GcJpo5PECMFxYDlZ2z1jD1xCMuo6u47xkdUfFVZDj -/bpV6wfEU6s3qe4hsiFbYI89MvHVI5TWWA== ------END CERTIFICATE----- - -# Issuer: CN=CommScope Public Trust ECC Root-01 O=CommScope -# Subject: CN=CommScope Public Trust ECC Root-01 O=CommScope -# Label: "CommScope Public Trust ECC Root-01" -# Serial: 385011430473757362783587124273108818652468453534 -# MD5 Fingerprint: 3a:40:a7:fc:03:8c:9c:38:79:2f:3a:a2:6c:b6:0a:16 -# SHA1 Fingerprint: 07:86:c0:d8:dd:8e:c0:80:98:06:98:d0:58:7a:ef:de:a6:cc:a2:5d -# SHA256 Fingerprint: 11:43:7c:da:7b:b4:5e:41:36:5f:45:b3:9a:38:98:6b:0d:e0:0d:ef:34:8e:0c:7b:b0:87:36:33:80:0b:c3:8b ------BEGIN CERTIFICATE----- -MIICHTCCAaOgAwIBAgIUQ3CCd89NXTTxyq4yLzf39H91oJ4wCgYIKoZIzj0EAwMw -TjELMAkGA1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29t -bVNjb3BlIFB1YmxpYyBUcnVzdCBFQ0MgUm9vdC0wMTAeFw0yMTA0MjgxNzM1NDNa -Fw00NjA0MjgxNzM1NDJaME4xCzAJBgNVBAYTAlVTMRIwEAYDVQQKDAlDb21tU2Nv -cGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1c3QgRUNDIFJvb3QtMDEw -djAQBgcqhkjOPQIBBgUrgQQAIgNiAARLNumuV16ocNfQj3Rid8NeeqrltqLxeP0C -flfdkXmcbLlSiFS8LwS+uM32ENEp7LXQoMPwiXAZu1FlxUOcw5tjnSCDPgYLpkJE -hRGnSjot6dZoL0hOUysHP029uax3OVejQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD -VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSOB2LAUN3GGQYARnQE9/OufXVNMDAKBggq -hkjOPQQDAwNoADBlAjEAnDPfQeMjqEI2Jpc1XHvr20v4qotzVRVcrHgpD7oh2MSg -2NED3W3ROT3Ek2DS43KyAjB8xX6I01D1HiXo+k515liWpDVfG2XqYZpwI7UNo5uS -Um9poIyNStDuiw7LR47QjRE= ------END CERTIFICATE----- - -# Issuer: CN=CommScope Public Trust ECC Root-02 O=CommScope -# Subject: CN=CommScope Public Trust ECC Root-02 O=CommScope -# Label: "CommScope Public Trust ECC Root-02" -# Serial: 234015080301808452132356021271193974922492992893 -# MD5 Fingerprint: 59:b0:44:d5:65:4d:b8:5c:55:19:92:02:b6:d1:94:b2 -# SHA1 Fingerprint: 3c:3f:ef:57:0f:fe:65:93:86:9e:a0:fe:b0:f6:ed:8e:d1:13:c7:e5 -# SHA256 Fingerprint: 2f:fb:7f:81:3b:bb:b3:c8:9a:b4:e8:16:2d:0f:16:d7:15:09:a8:30:cc:9d:73:c2:62:e5:14:08:75:d1:ad:4a ------BEGIN CERTIFICATE----- -MIICHDCCAaOgAwIBAgIUKP2ZYEFHpgE6yhR7H+/5aAiDXX0wCgYIKoZIzj0EAwMw -TjELMAkGA1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwiQ29t -bVNjb3BlIFB1YmxpYyBUcnVzdCBFQ0MgUm9vdC0wMjAeFw0yMTA0MjgxNzQ0NTRa -Fw00NjA0MjgxNzQ0NTNaME4xCzAJBgNVBAYTAlVTMRIwEAYDVQQKDAlDb21tU2Nv -cGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1c3QgRUNDIFJvb3QtMDIw -djAQBgcqhkjOPQIBBgUrgQQAIgNiAAR4MIHoYx7l63FRD/cHB8o5mXxO1Q/MMDAL -j2aTPs+9xYa9+bG3tD60B8jzljHz7aRP+KNOjSkVWLjVb3/ubCK1sK9IRQq9qEmU -v4RDsNuESgMjGWdqb8FuvAY5N9GIIvejQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYD -VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTmGHX/72DehKT1RsfeSlXjMjZ59TAKBggq -hkjOPQQDAwNnADBkAjAmc0l6tqvmSfR9Uj/UQQSugEODZXW5hYA4O9Zv5JOGq4/n -ich/m35rChJVYaoR4HkCMHfoMXGsPHED1oQmHhS48zs73u1Z/GtMMH9ZzkXpc2AV -mkzw5l4lIhVtwodZ0LKOag== ------END CERTIFICATE----- - -# Issuer: CN=CommScope Public Trust RSA Root-01 O=CommScope -# Subject: CN=CommScope Public Trust RSA Root-01 O=CommScope -# Label: "CommScope Public Trust RSA Root-01" -# Serial: 354030733275608256394402989253558293562031411421 -# MD5 Fingerprint: 0e:b4:15:bc:87:63:5d:5d:02:73:d4:26:38:68:73:d8 -# SHA1 Fingerprint: 6d:0a:5f:f7:b4:23:06:b4:85:b3:b7:97:64:fc:ac:75:f5:33:f2:93 -# SHA256 Fingerprint: 02:bd:f9:6e:2a:45:dd:9b:f1:8f:c7:e1:db:df:21:a0:37:9b:a3:c9:c2:61:03:44:cf:d8:d6:06:fe:c1:ed:81 ------BEGIN CERTIFICATE----- -MIIFbDCCA1SgAwIBAgIUPgNJgXUWdDGOTKvVxZAplsU5EN0wDQYJKoZIhvcNAQEL -BQAwTjELMAkGA1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwi -Q29tbVNjb3BlIFB1YmxpYyBUcnVzdCBSU0EgUm9vdC0wMTAeFw0yMTA0MjgxNjQ1 -NTRaFw00NjA0MjgxNjQ1NTNaME4xCzAJBgNVBAYTAlVTMRIwEAYDVQQKDAlDb21t -U2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1c3QgUlNBIFJvb3Qt -MDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCwSGWjDR1C45FtnYSk -YZYSwu3D2iM0GXb26v1VWvZVAVMP8syMl0+5UMuzAURWlv2bKOx7dAvnQmtVzslh -suitQDy6uUEKBU8bJoWPQ7VAtYXR1HHcg0Hz9kXHgKKEUJdGzqAMxGBWBB0HW0al -DrJLpA6lfO741GIDuZNqihS4cPgugkY4Iw50x2tBt9Apo52AsH53k2NC+zSDO3Oj -WiE260f6GBfZumbCk6SP/F2krfxQapWsvCQz0b2If4b19bJzKo98rwjyGpg/qYFl -P8GMicWWMJoKz/TUyDTtnS+8jTiGU+6Xn6myY5QXjQ/cZip8UlF1y5mO6D1cv547 -KI2DAg+pn3LiLCuz3GaXAEDQpFSOm117RTYm1nJD68/A6g3czhLmfTifBSeolz7p -UcZsBSjBAg/pGG3svZwG1KdJ9FQFa2ww8esD1eo9anbCyxooSU1/ZOD6K9pzg4H/ -kQO9lLvkuI6cMmPNn7togbGEW682v3fuHX/3SZtS7NJ3Wn2RnU3COS3kuoL4b/JO -Hg9O5j9ZpSPcPYeoKFgo0fEbNttPxP/hjFtyjMcmAyejOQoBqsCyMWCDIqFPEgkB -Ea801M/XrmLTBQe0MXXgDW1XT2mH+VepuhX2yFJtocucH+X8eKg1mp9BFM6ltM6U -CBwJrVbl2rZJmkrqYxhTnCwuwwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G -A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUN12mmnQywsL5x6YVEFm45P3luG0wDQYJ -KoZIhvcNAQELBQADggIBAK+nz97/4L1CjU3lIpbfaOp9TSp90K09FlxD533Ahuh6 -NWPxzIHIxgvoLlI1pKZJkGNRrDSsBTtXAOnTYtPZKdVUvhwQkZyybf5Z/Xn36lbQ -nmhUQo8mUuJM3y+Xpi/SB5io82BdS5pYV4jvguX6r2yBS5KPQJqTRlnLX3gWsWc+ -QgvfKNmwrZggvkN80V4aCRckjXtdlemrwWCrWxhkgPut4AZ9HcpZuPN4KWfGVh2v -trV0KnahP/t1MJ+UXjulYPPLXAziDslg+MkfFoom3ecnf+slpoq9uC02EJqxWE2a -aE9gVOX2RhOOiKy8IUISrcZKiX2bwdgt6ZYD9KJ0DLwAHb/WNyVntHKLr4W96ioD -j8z7PEQkguIBpQtZtjSNMgsSDesnwv1B10A8ckYpwIzqug/xBpMu95yo9GA+o/E4 -Xo4TwbM6l4c/ksp4qRyv0LAbJh6+cOx69TOY6lz/KwsETkPdY34Op054A5U+1C0w -lREQKC6/oAI+/15Z0wUOlV9TRe9rh9VIzRamloPh37MG88EU26fsHItdkJANclHn -YfkUyq+Dj7+vsQpZXdxc1+SWrVtgHdqul7I52Qb1dgAT+GhMIbA1xNxVssnBQVoc -icCMb3SgazNNtQEo/a2tiRc7ppqEvOuM6sRxJKi6KfkIsidWNTJf6jn7MZrVGczw ------END CERTIFICATE----- - -# Issuer: CN=CommScope Public Trust RSA Root-02 O=CommScope -# Subject: CN=CommScope Public Trust RSA Root-02 O=CommScope -# Label: "CommScope Public Trust RSA Root-02" -# Serial: 480062499834624527752716769107743131258796508494 -# MD5 Fingerprint: e1:29:f9:62:7b:76:e2:96:6d:f3:d4:d7:0f:ae:1f:aa -# SHA1 Fingerprint: ea:b0:e2:52:1b:89:93:4c:11:68:f2:d8:9a:ac:22:4c:a3:8a:57:ae -# SHA256 Fingerprint: ff:e9:43:d7:93:42:4b:4f:7c:44:0c:1c:3d:64:8d:53:63:f3:4b:82:dc:87:aa:7a:9f:11:8f:c5:de:e1:01:f1 ------BEGIN CERTIFICATE----- -MIIFbDCCA1SgAwIBAgIUVBa/O345lXGN0aoApYYNK496BU4wDQYJKoZIhvcNAQEL -BQAwTjELMAkGA1UEBhMCVVMxEjAQBgNVBAoMCUNvbW1TY29wZTErMCkGA1UEAwwi -Q29tbVNjb3BlIFB1YmxpYyBUcnVzdCBSU0EgUm9vdC0wMjAeFw0yMTA0MjgxNzE2 -NDNaFw00NjA0MjgxNzE2NDJaME4xCzAJBgNVBAYTAlVTMRIwEAYDVQQKDAlDb21t -U2NvcGUxKzApBgNVBAMMIkNvbW1TY29wZSBQdWJsaWMgVHJ1c3QgUlNBIFJvb3Qt -MDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDh+g77aAASyE3VrCLE -NQE7xVTlWXZjpX/rwcRqmL0yjReA61260WI9JSMZNRTpf4mnG2I81lDnNJUDMrG0 -kyI9p+Kx7eZ7Ti6Hmw0zdQreqjXnfuU2mKKuJZ6VszKWpCtYHu8//mI0SFHRtI1C -rWDaSWqVcN3SAOLMV2MCe5bdSZdbkk6V0/nLKR8YSvgBKtJjCW4k6YnS5cciTNxz -hkcAqg2Ijq6FfUrpuzNPDlJwnZXjfG2WWy09X6GDRl224yW4fKcZgBzqZUPckXk2 -LHR88mcGyYnJ27/aaL8j7dxrrSiDeS/sOKUNNwFnJ5rpM9kzXzehxfCrPfp4sOcs -n/Y+n2Dg70jpkEUeBVF4GiwSLFworA2iI540jwXmojPOEXcT1A6kHkIfhs1w/tku -FT0du7jyU1fbzMZ0KZwYszZ1OC4PVKH4kh+Jlk+71O6d6Ts2QrUKOyrUZHk2EOH5 -kQMreyBUzQ0ZGshBMjTRsJnhkB4BQDa1t/qp5Xd1pCKBXbCL5CcSD1SIxtuFdOa3 -wNemKfrb3vOTlycEVS8KbzfFPROvCgCpLIscgSjX74Yxqa7ybrjKaixUR9gqiC6v -wQcQeKwRoi9C8DfF8rhW3Q5iLc4tVn5V8qdE9isy9COoR+jUKgF4z2rDN6ieZdIs -5fq6M8EGRPbmz6UNp2YINIos8wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G -A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUR9DnsSL/nSz12Vdgs7GxcJXvYXowDQYJ -KoZIhvcNAQELBQADggIBAIZpsU0v6Z9PIpNojuQhmaPORVMbc0RTAIFhzTHjCLqB -KCh6krm2qMhDnscTJk3C2OVVnJJdUNjCK9v+5qiXz1I6JMNlZFxHMaNlNRPDk7n3 -+VGXu6TwYofF1gbTl4MgqX67tiHCpQ2EAOHyJxCDut0DgdXdaMNmEMjRdrSzbyme -APnCKfWxkxlSaRosTKCL4BWaMS/TiJVZbuXEs1DIFAhKm4sTg7GkcrI7djNB3Nyq -pgdvHSQSn8h2vS/ZjvQs7rfSOBAkNlEv41xdgSGn2rtO/+YHqP65DSdsu3BaVXoT -6fEqSWnHX4dXTEN5bTpl6TBcQe7rd6VzEojov32u5cSoHw2OHG1QAk8mGEPej1WF -sQs3BWDJVTkSBKEqz3EWnzZRSb9wO55nnPt7eck5HHisd5FUmrh1CoFSl+NmYWvt -PjgelmFV4ZFUjO2MJB+ByRCac5krFk5yAD9UG/iNuovnFNa2RU9g7Jauwy8CTl2d -lklyALKrdVwPaFsdZcJfMw8eD/A7hvWwTruc9+olBdytoptLFwG+Qt81IR2tq670 -v64fG9PiO/yzcnMcmyiQiRM9HcEARwmWmjgb3bHPDcK0RPOWlc4yOo80nOAXx17O -rg3bhzjlP1v9mxnhMUF6cKojawHhRUzNlM47ni3niAIi9G7oyOzWPPO5std3eqx7 ------END CERTIFICATE----- - -# Issuer: CN=Telekom Security TLS ECC Root 2020 O=Deutsche Telekom Security GmbH -# Subject: CN=Telekom Security TLS ECC Root 2020 O=Deutsche Telekom Security GmbH -# Label: "Telekom Security TLS ECC Root 2020" -# Serial: 72082518505882327255703894282316633856 -# MD5 Fingerprint: c1:ab:fe:6a:10:2c:03:8d:bc:1c:22:32:c0:85:a7:fd -# SHA1 Fingerprint: c0:f8:96:c5:a9:3b:01:06:21:07:da:18:42:48:bc:e9:9d:88:d5:ec -# SHA256 Fingerprint: 57:8a:f4:de:d0:85:3f:4e:59:98:db:4a:ea:f9:cb:ea:8d:94:5f:60:b6:20:a3:8d:1a:3c:13:b2:bc:7b:a8:e1 ------BEGIN CERTIFICATE----- -MIICQjCCAcmgAwIBAgIQNjqWjMlcsljN0AFdxeVXADAKBggqhkjOPQQDAzBjMQsw -CQYDVQQGEwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0eSBH -bWJIMSswKQYDVQQDDCJUZWxla29tIFNlY3VyaXR5IFRMUyBFQ0MgUm9vdCAyMDIw -MB4XDTIwMDgyNTA3NDgyMFoXDTQ1MDgyNTIzNTk1OVowYzELMAkGA1UEBhMCREUx -JzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJpdHkgR21iSDErMCkGA1UE -AwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgRUNDIFJvb3QgMjAyMDB2MBAGByqGSM49 -AgEGBSuBBAAiA2IABM6//leov9Wq9xCazbzREaK9Z0LMkOsVGJDZos0MKiXrPk/O -tdKPD/M12kOLAoC+b1EkHQ9rK8qfwm9QMuU3ILYg/4gND21Ju9sGpIeQkpT0CdDP -f8iAC8GXs7s1J8nCG6NCMEAwHQYDVR0OBBYEFONyzG6VmUex5rNhTNHLq+O6zd6f -MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2cA -MGQCMHVSi7ekEE+uShCLsoRbQuHmKjYC2qBuGT8lv9pZMo7k+5Dck2TOrbRBR2Di -z6fLHgIwN0GMZt9Ba9aDAEH9L1r3ULRn0SyocddDypwnJJGDSA3PzfdUga/sf+Rn -27iQ7t0l ------END CERTIFICATE----- - -# Issuer: CN=Telekom Security TLS RSA Root 2023 O=Deutsche Telekom Security GmbH -# Subject: CN=Telekom Security TLS RSA Root 2023 O=Deutsche Telekom Security GmbH -# Label: "Telekom Security TLS RSA Root 2023" -# Serial: 44676229530606711399881795178081572759 -# MD5 Fingerprint: bf:5b:eb:54:40:cd:48:71:c4:20:8d:7d:de:0a:42:f2 -# SHA1 Fingerprint: 54:d3:ac:b3:bd:57:56:f6:85:9d:ce:e5:c3:21:e2:d4:ad:83:d0:93 -# SHA256 Fingerprint: ef:c6:5c:ad:bb:59:ad:b6:ef:e8:4d:a2:23:11:b3:56:24:b7:1b:3b:1e:a0:da:8b:66:55:17:4e:c8:97:86:46 ------BEGIN CERTIFICATE----- -MIIFszCCA5ugAwIBAgIQIZxULej27HF3+k7ow3BXlzANBgkqhkiG9w0BAQwFADBj -MQswCQYDVQQGEwJERTEnMCUGA1UECgweRGV1dHNjaGUgVGVsZWtvbSBTZWN1cml0 -eSBHbWJIMSswKQYDVQQDDCJUZWxla29tIFNlY3VyaXR5IFRMUyBSU0EgUm9vdCAy -MDIzMB4XDTIzMDMyODEyMTY0NVoXDTQ4MDMyNzIzNTk1OVowYzELMAkGA1UEBhMC -REUxJzAlBgNVBAoMHkRldXRzY2hlIFRlbGVrb20gU2VjdXJpdHkgR21iSDErMCkG -A1UEAwwiVGVsZWtvbSBTZWN1cml0eSBUTFMgUlNBIFJvb3QgMjAyMzCCAiIwDQYJ -KoZIhvcNAQEBBQADggIPADCCAgoCggIBAO01oYGA88tKaVvC+1GDrib94W7zgRJ9 -cUD/h3VCKSHtgVIs3xLBGYSJwb3FKNXVS2xE1kzbB5ZKVXrKNoIENqil/Cf2SfHV -cp6R+SPWcHu79ZvB7JPPGeplfohwoHP89v+1VmLhc2o0mD6CuKyVU/QBoCcHcqMA -U6DksquDOFczJZSfvkgdmOGjup5czQRxUX11eKvzWarE4GC+j4NSuHUaQTXtvPM6 -Y+mpFEXX5lLRbtLevOP1Czvm4MS9Q2QTps70mDdsipWol8hHD/BeEIvnHRz+sTug -BTNoBUGCwQMrAcjnj02r6LX2zWtEtefdi+zqJbQAIldNsLGyMcEWzv/9FIS3R/qy -8XDe24tsNlikfLMR0cN3f1+2JeANxdKz+bi4d9s3cXFH42AYTyS2dTd4uaNir73J -co4vzLuu2+QVUhkHM/tqty1LkCiCc/4YizWN26cEar7qwU02OxY2kTLvtkCJkUPg -8qKrBC7m8kwOFjQgrIfBLX7JZkcXFBGk8/ehJImr2BrIoVyxo/eMbcgByU/J7MT8 -rFEz0ciD0cmfHdRHNCk+y7AO+oMLKFjlKdw/fKifybYKu6boRhYPluV75Gp6SG12 -mAWl3G0eQh5C2hrgUve1g8Aae3g1LDj1H/1Joy7SWWO/gLCMk3PLNaaZlSJhZQNg -+y+TS/qanIA7AgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUtqeX -gj10hZv3PJ+TmpV5dVKMbUcwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBS2 -p5eCPXSFm/c8n5OalXl1UoxtRzANBgkqhkiG9w0BAQwFAAOCAgEAqMxhpr51nhVQ -pGv7qHBFfLp+sVr8WyP6Cnf4mHGCDG3gXkaqk/QeoMPhk9tLrbKmXauw1GLLXrtm -9S3ul0A8Yute1hTWjOKWi0FpkzXmuZlrYrShF2Y0pmtjxrlO8iLpWA1WQdH6DErw -M807u20hOq6OcrXDSvvpfeWxm4bu4uB9tPcy/SKE8YXJN3nptT+/XOR0so8RYgDd -GGah2XsjX/GO1WfoVNpbOms2b/mBsTNHM3dA+VKq3dSDz4V4mZqTuXNnQkYRIer+ -CqkbGmVps4+uFrb2S1ayLfmlyOw7YqPta9BO1UAJpB+Y1zqlklkg5LB9zVtzaL1t -xKITDmcZuI1CfmwMmm6gJC3VRRvcxAIU/oVbZZfKTpBQCHpCNfnqwmbU+AGuHrS+ -w6jv/naaoqYfRvaE7fzbzsQCzndILIyy7MMAo+wsVRjBfhnu4S/yrYObnqsZ38aK -L4x35bcF7DvB7L6Gs4a8wPfc5+pbrrLMtTWGS9DiP7bY+A4A7l3j941Y/8+LN+lj -X273CXE2whJdV/LItM3z7gLfEdxquVeEHVlNjM7IDiPCtyaaEBRx/pOyiriA8A4Q -ntOoUAw3gi/q4Iqd4Sw5/7W0cwDk90imc6y/st53BIe0o82bNSQ3+pCTE4FCxpgm -dTdmQRCsu/WU48IxK63nI1bMNSWSs1A= ------END CERTIFICATE----- - -# Issuer: CN=FIRMAPROFESIONAL CA ROOT-A WEB O=Firmaprofesional SA -# Subject: CN=FIRMAPROFESIONAL CA ROOT-A WEB O=Firmaprofesional SA -# Label: "FIRMAPROFESIONAL CA ROOT-A WEB" -# Serial: 65916896770016886708751106294915943533 -# MD5 Fingerprint: 82:b2:ad:45:00:82:b0:66:63:f8:5f:c3:67:4e:ce:a3 -# SHA1 Fingerprint: a8:31:11:74:a6:14:15:0d:ca:77:dd:0e:e4:0c:5d:58:fc:a0:72:a5 -# SHA256 Fingerprint: be:f2:56:da:f2:6e:9c:69:bd:ec:16:02:35:97:98:f3:ca:f7:18:21:a0:3e:01:82:57:c5:3c:65:61:7f:3d:4a ------BEGIN CERTIFICATE----- -MIICejCCAgCgAwIBAgIQMZch7a+JQn81QYehZ1ZMbTAKBggqhkjOPQQDAzBuMQsw -CQYDVQQGEwJFUzEcMBoGA1UECgwTRmlybWFwcm9mZXNpb25hbCBTQTEYMBYGA1UE -YQwPVkFURVMtQTYyNjM0MDY4MScwJQYDVQQDDB5GSVJNQVBST0ZFU0lPTkFMIENB -IFJPT1QtQSBXRUIwHhcNMjIwNDA2MDkwMTM2WhcNNDcwMzMxMDkwMTM2WjBuMQsw -CQYDVQQGEwJFUzEcMBoGA1UECgwTRmlybWFwcm9mZXNpb25hbCBTQTEYMBYGA1UE -YQwPVkFURVMtQTYyNjM0MDY4MScwJQYDVQQDDB5GSVJNQVBST0ZFU0lPTkFMIENB -IFJPT1QtQSBXRUIwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARHU+osEaR3xyrq89Zf -e9MEkVz6iMYiuYMQYneEMy3pA4jU4DP37XcsSmDq5G+tbbT4TIqk5B/K6k84Si6C -cyvHZpsKjECcfIr28jlgst7L7Ljkb+qbXbdTkBgyVcUgt5SjYzBhMA8GA1UdEwEB -/wQFMAMBAf8wHwYDVR0jBBgwFoAUk+FDY1w8ndYn81LsF7Kpryz3dvgwHQYDVR0O -BBYEFJPhQ2NcPJ3WJ/NS7Beyqa8s93b4MA4GA1UdDwEB/wQEAwIBBjAKBggqhkjO -PQQDAwNoADBlAjAdfKR7w4l1M+E7qUW/Runpod3JIha3RxEL2Jq68cgLcFBTApFw -hVmpHqTm6iMxoAACMQD94vizrxa5HnPEluPBMBnYfubDl94cT7iJLzPrSA8Z94dG -XSaQpYXFuXqUPoeovQA= ------END CERTIFICATE----- - -# Issuer: CN=TWCA CYBER Root CA O=TAIWAN-CA OU=Root CA -# Subject: CN=TWCA CYBER Root CA O=TAIWAN-CA OU=Root CA -# Label: "TWCA CYBER Root CA" -# Serial: 85076849864375384482682434040119489222 -# MD5 Fingerprint: 0b:33:a0:97:52:95:d4:a9:fd:bb:db:6e:a3:55:5b:51 -# SHA1 Fingerprint: f6:b1:1c:1a:83:38:e9:7b:db:b3:a8:c8:33:24:e0:2d:9c:7f:26:66 -# SHA256 Fingerprint: 3f:63:bb:28:14:be:17:4e:c8:b6:43:9c:f0:8d:6d:56:f0:b7:c4:05:88:3a:56:48:a3:34:42:4d:6b:3e:c5:58 ------BEGIN CERTIFICATE----- -MIIFjTCCA3WgAwIBAgIQQAE0jMIAAAAAAAAAATzyxjANBgkqhkiG9w0BAQwFADBQ -MQswCQYDVQQGEwJUVzESMBAGA1UEChMJVEFJV0FOLUNBMRAwDgYDVQQLEwdSb290 -IENBMRswGQYDVQQDExJUV0NBIENZQkVSIFJvb3QgQ0EwHhcNMjIxMTIyMDY1NDI5 -WhcNNDcxMTIyMTU1OTU5WjBQMQswCQYDVQQGEwJUVzESMBAGA1UEChMJVEFJV0FO -LUNBMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJUV0NBIENZQkVSIFJvb3Qg -Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDG+Moe2Qkgfh1sTs6P -40czRJzHyWmqOlt47nDSkvgEs1JSHWdyKKHfi12VCv7qze33Kc7wb3+szT3vsxxF -avcokPFhV8UMxKNQXd7UtcsZyoC5dc4pztKFIuwCY8xEMCDa6pFbVuYdHNWdZsc/ -34bKS1PE2Y2yHer43CdTo0fhYcx9tbD47nORxc5zb87uEB8aBs/pJ2DFTxnk684i -JkXXYJndzk834H/nY62wuFm40AZoNWDTNq5xQwTxaWV4fPMf88oon1oglWa0zbfu -j3ikRRjpJi+NmykosaS3Om251Bw4ckVYsV7r8Cibt4LK/c/WMw+f+5eesRycnupf -Xtuq3VTpMCEobY5583WSjCb+3MX2w7DfRFlDo7YDKPYIMKoNM+HvnKkHIuNZW0CP -2oi3aQiotyMuRAlZN1vH4xfyIutuOVLF3lSnmMlLIJXcRolftBL5hSmO68gnFSDA -S9TMfAxsNAwmmyYxpjyn9tnQS6Jk/zuZQXLB4HCX8SS7K8R0IrGsayIyJNN4KsDA -oS/xUgXJP+92ZuJF2A09rZXIx4kmyA+upwMu+8Ff+iDhcK2wZSA3M2Cw1a/XDBzC -kHDXShi8fgGwsOsVHkQGzaRP6AzRwyAQ4VRlnrZR0Bp2a0JaWHY06rc3Ga4udfmW -5cFZ95RXKSWNOkyrTZpB0F8mAwIDAQABo2MwYTAOBgNVHQ8BAf8EBAMCAQYwDwYD -VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBSdhWEUfMFib5do5E83QOGt4A1WNzAd -BgNVHQ4EFgQUnYVhFHzBYm+XaORPN0DhreANVjcwDQYJKoZIhvcNAQEMBQADggIB -AGSPesRiDrWIzLjHhg6hShbNcAu3p4ULs3a2D6f/CIsLJc+o1IN1KriWiLb73y0t -tGlTITVX1olNc79pj3CjYcya2x6a4CD4bLubIp1dhDGaLIrdaqHXKGnK/nZVekZn -68xDiBaiA9a5F/gZbG0jAn/xX9AKKSM70aoK7akXJlQKTcKlTfjF/biBzysseKNn -TKkHmvPfXvt89YnNdJdhEGoHK4Fa0o635yDRIG4kqIQnoVesqlVYL9zZyvpoBJ7t -RCT5dEA7IzOrg1oYJkK2bVS1FmAwbLGg+LhBoF1JSdJlBTrq/p1hvIbZv97Tujqx -f36SNI7JAG7cmL3c7IAFrQI932XtCwP39xaEBDG6k5TY8hL4iuO/Qq+n1M0RFxbI -Qh0UqEL20kCGoE8jypZFVmAGzbdVAaYBlGX+bgUJurSkquLvWL69J1bY73NxW0Qz -8ppy6rBePm6pUlvscG21h483XjyMnM7k8M4MZ0HMzvaAq07MTFb1wWFZk7Q+ptq4 -NxKfKjLji7gh7MMrZQzvIt6IKTtM1/r+t+FHvpw+PoP7UV31aPcuIYXcv/Fa4nzX -xeSDwWrruoBa3lwtcHb4yOWHh8qgnaHlIhInD0Q9HWzq1MKLL295q39QpsQZp6F6 -t5b5wR9iWqJDB0BeJsas7a5wFsWqynKKTbDPAYsDP27X ------END CERTIFICATE----- - -# Issuer: CN=SecureSign Root CA12 O=Cybertrust Japan Co., Ltd. -# Subject: CN=SecureSign Root CA12 O=Cybertrust Japan Co., Ltd. -# Label: "SecureSign Root CA12" -# Serial: 587887345431707215246142177076162061960426065942 -# MD5 Fingerprint: c6:89:ca:64:42:9b:62:08:49:0b:1e:7f:e9:07:3d:e8 -# SHA1 Fingerprint: 7a:22:1e:3d:de:1b:06:ac:9e:c8:47:70:16:8e:3c:e5:f7:6b:06:f4 -# SHA256 Fingerprint: 3f:03:4b:b5:70:4d:44:b2:d0:85:45:a0:20:57:de:93:eb:f3:90:5f:ce:72:1a:cb:c7:30:c0:6d:da:ee:90:4e ------BEGIN CERTIFICATE----- -MIIDcjCCAlqgAwIBAgIUZvnHwa/swlG07VOX5uaCwysckBYwDQYJKoZIhvcNAQEL -BQAwUTELMAkGA1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28u -LCBMdGQuMR0wGwYDVQQDExRTZWN1cmVTaWduIFJvb3QgQ0ExMjAeFw0yMDA0MDgw -NTM2NDZaFw00MDA0MDgwNTM2NDZaMFExCzAJBgNVBAYTAkpQMSMwIQYDVQQKExpD -eWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMUU2VjdXJlU2lnbiBS -b290IENBMTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC6OcE3emhF -KxS06+QT61d1I02PJC0W6K6OyX2kVzsqdiUzg2zqMoqUm048luT9Ub+ZyZN+v/mt -p7JIKwccJ/VMvHASd6SFVLX9kHrko+RRWAPNEHl57muTH2SOa2SroxPjcf59q5zd -J1M3s6oYwlkm7Fsf0uZlfO+TvdhYXAvA42VvPMfKWeP+bl+sg779XSVOKik71gur -FzJ4pOE+lEa+Ym6b3kaosRbnhW70CEBFEaCeVESE99g2zvVQR9wsMJvuwPWW0v4J -hscGWa5Pro4RmHvzC1KqYiaqId+OJTN5lxZJjfU+1UefNzFJM3IFTQy2VYzxV4+K -h9GtxRESOaCtAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD -AgEGMB0GA1UdDgQWBBRXNPN0zwRL1SXm8UC2LEzZLemgrTANBgkqhkiG9w0BAQsF -AAOCAQEAPrvbFxbS8hQBICw4g0utvsqFepq2m2um4fylOqyttCg6r9cBg0krY6Ld -mmQOmFxv3Y67ilQiLUoT865AQ9tPkbeGGuwAtEGBpE/6aouIs3YIcipJQMPTw4WJ -mBClnW8Zt7vPemVV2zfrPIpyMpcemik+rY3moxtt9XUa5rBouVui7mlHJzWhhpmA -8zNL4WukJsPvdFlseqJkth5Ew1DgDzk9qTPxpfPSvWKErI4cqc1avTc7bgoitPQV -55FYxTpE05Uo2cBl6XLK0A+9H7MV2anjpEcJnuDLN/v9vZfVvhgaaaI5gdka9at/ -yOPiZwud9AzqVN/Ssq+xIvEg37xEHA== ------END CERTIFICATE----- - -# Issuer: CN=SecureSign Root CA14 O=Cybertrust Japan Co., Ltd. -# Subject: CN=SecureSign Root CA14 O=Cybertrust Japan Co., Ltd. -# Label: "SecureSign Root CA14" -# Serial: 575790784512929437950770173562378038616896959179 -# MD5 Fingerprint: 71:0d:72:fa:92:19:65:5e:89:04:ac:16:33:f0:bc:d5 -# SHA1 Fingerprint: dd:50:c0:f7:79:b3:64:2e:74:a2:b8:9d:9f:d3:40:dd:bb:f0:f2:4f -# SHA256 Fingerprint: 4b:00:9c:10:34:49:4f:9a:b5:6b:ba:3b:a1:d6:27:31:fc:4d:20:d8:95:5a:dc:ec:10:a9:25:60:72:61:e3:38 ------BEGIN CERTIFICATE----- -MIIFcjCCA1qgAwIBAgIUZNtaDCBO6Ncpd8hQJ6JaJ90t8sswDQYJKoZIhvcNAQEM -BQAwUTELMAkGA1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28u -LCBMdGQuMR0wGwYDVQQDExRTZWN1cmVTaWduIFJvb3QgQ0ExNDAeFw0yMDA0MDgw -NzA2MTlaFw00NTA0MDgwNzA2MTlaMFExCzAJBgNVBAYTAkpQMSMwIQYDVQQKExpD -eWJlcnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMUU2VjdXJlU2lnbiBS -b290IENBMTQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDF0nqh1oq/ -FjHQmNE6lPxauG4iwWL3pwon71D2LrGeaBLwbCRjOfHw3xDG3rdSINVSW0KZnvOg -vlIfX8xnbacuUKLBl422+JX1sLrcneC+y9/3OPJH9aaakpUqYllQC6KxNedlsmGy -6pJxaeQp8E+BgQQ8sqVb1MWoWWd7VRxJq3qdwudzTe/NCcLEVxLbAQ4jeQkHO6Lo -/IrPj8BGJJw4J+CDnRugv3gVEOuGTgpa/d/aLIJ+7sr2KeH6caH3iGicnPCNvg9J -kdjqOvn90Ghx2+m1K06Ckm9mH+Dw3EzsytHqunQG+bOEkJTRX45zGRBdAuVwpcAQ -0BB8b8VYSbSwbprafZX1zNoCr7gsfXmPvkPx+SgojQlD+Ajda8iLLCSxjVIHvXib -y8posqTdDEx5YMaZ0ZPxMBoH064iwurO8YQJzOAUbn8/ftKChazcqRZOhaBgy/ac -18izju3Gm5h1DVXoX+WViwKkrkMpKBGk5hIwAUt1ax5mnXkvpXYvHUC0bcl9eQjs -0Wq2XSqypWa9a4X0dFbD9ed1Uigspf9mR6XU/v6eVL9lfgHWMI+lNpyiUBzuOIAB -SMbHdPTGrMNASRZhdCyvjG817XsYAFs2PJxQDcqSMxDxJklt33UkN4Ii1+iW/RVL -ApY+B3KVfqs9TC7XyvDf4Fg/LS8EmjijAQIDAQABo0IwQDAPBgNVHRMBAf8EBTAD -AQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUBpOjCl4oaTeqYR3r6/wtbyPk -86AwDQYJKoZIhvcNAQEMBQADggIBAJaAcgkGfpzMkwQWu6A6jZJOtxEaCnFxEM0E -rX+lRVAQZk5KQaID2RFPeje5S+LGjzJmdSX7684/AykmjbgWHfYfM25I5uj4V7Ib -ed87hwriZLoAymzvftAj63iP/2SbNDefNWWipAA9EiOWWF3KY4fGoweITedpdopT -zfFP7ELyk+OZpDc8h7hi2/DsHzc/N19DzFGdtfCXwreFamgLRB7lUe6TzktuhsHS -DCRZNhqfLJGP4xjblJUK7ZGqDpncllPjYYPGFrojutzdfhrGe0K22VoF3Jpf1d+4 -2kd92jjbrDnVHmtsKheMYc2xbXIBw8MgAGJoFjHVdqqGuw6qnsb58Nn4DSEC5MUo -FlkRudlpcyqSeLiSV5sI8jrlL5WwWLdrIBRtFO8KvH7YVdiI2i/6GaX7i+B/OfVy -K4XELKzvGUWSTLNhB9xNH27SgRNcmvMSZ4PPmz+Ln52kuaiWA3rF7iDeM9ovnhp6 -dB7h7sxaOgTdsxoEqBRjrLdHEoOabPXm6RUVkRqEGQ6UROcSjiVbgGcZ3GOTEAtl -Lor6CZpO2oYofaphNdgOpygau1LgePhsumywbrmHXumZNTfxPWQrqaA0k89jL9WB -365jJ6UeTo3cKXhZ+PmhIIynJkBugnLNeLLIjzwec+fBH7/PzqUqm9tEZDKgu39c -JRNItX+S ------END CERTIFICATE----- - -# Issuer: CN=SecureSign Root CA15 O=Cybertrust Japan Co., Ltd. -# Subject: CN=SecureSign Root CA15 O=Cybertrust Japan Co., Ltd. -# Label: "SecureSign Root CA15" -# Serial: 126083514594751269499665114766174399806381178503 -# MD5 Fingerprint: 13:30:fc:c4:62:a6:a9:de:b5:c1:68:af:b5:d2:31:47 -# SHA1 Fingerprint: cb:ba:83:c8:c1:5a:5d:f1:f9:73:6f:ca:d7:ef:28:13:06:4a:07:7d -# SHA256 Fingerprint: e7:78:f0:f0:95:fe:84:37:29:cd:1a:00:82:17:9e:53:14:a9:c2:91:44:28:05:e1:fb:1d:8f:b6:b8:88:6c:3a ------BEGIN CERTIFICATE----- -MIICIzCCAamgAwIBAgIUFhXHw9hJp75pDIqI7fBw+d23PocwCgYIKoZIzj0EAwMw -UTELMAkGA1UEBhMCSlAxIzAhBgNVBAoTGkN5YmVydHJ1c3QgSmFwYW4gQ28uLCBM -dGQuMR0wGwYDVQQDExRTZWN1cmVTaWduIFJvb3QgQ0ExNTAeFw0yMDA0MDgwODMy -NTZaFw00NTA0MDgwODMyNTZaMFExCzAJBgNVBAYTAkpQMSMwIQYDVQQKExpDeWJl -cnRydXN0IEphcGFuIENvLiwgTHRkLjEdMBsGA1UEAxMUU2VjdXJlU2lnbiBSb290 -IENBMTUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQLUHSNZDKZmbPSYAi4Io5GdCx4 -wCtELW1fHcmuS1Iggz24FG1Th2CeX2yF2wYUleDHKP+dX+Sq8bOLbe1PL0vJSpSR -ZHX+AezB2Ot6lHhWGENfa4HL9rzatAy2KZMIaY+jQjBAMA8GA1UdEwEB/wQFMAMB -Af8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTrQciu/NWeUUj1vYv0hyCTQSvT -9DAKBggqhkjOPQQDAwNoADBlAjEA2S6Jfl5OpBEHvVnCB96rMjhTKkZEBhd6zlHp -4P9mLQlO4E/0BdGF9jVg3PVys0Z9AjBEmEYagoUeYWmJSwdLZrWeqrqgHkHZAXQ6 -bkU6iYAZezKYVWOr62Nuk22rGwlgMU4= ------END CERTIFICATE----- - -# Issuer: CN=D-TRUST BR Root CA 2 2023 O=D-Trust GmbH -# Subject: CN=D-TRUST BR Root CA 2 2023 O=D-Trust GmbH -# Label: "D-TRUST BR Root CA 2 2023" -# Serial: 153168538924886464690566649552453098598 -# MD5 Fingerprint: e1:09:ed:d3:60:d4:56:1b:47:1f:b7:0c:5f:1b:5f:85 -# SHA1 Fingerprint: 2d:b0:70:ee:71:94:af:69:68:17:db:79:ce:58:9f:a0:6b:96:f7:87 -# SHA256 Fingerprint: 05:52:e6:f8:3f:df:65:e8:fa:96:70:e6:66:df:28:a4:e2:13:40:b5:10:cb:e5:25:66:f9:7c:4f:b9:4b:2b:d1 ------BEGIN CERTIFICATE----- -MIIFqTCCA5GgAwIBAgIQczswBEhb2U14LnNLyaHcZjANBgkqhkiG9w0BAQ0FADBI -MQswCQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlE -LVRSVVNUIEJSIFJvb3QgQ0EgMiAyMDIzMB4XDTIzMDUwOTA4NTYzMVoXDTM4MDUw -OTA4NTYzMFowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEi -MCAGA1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDIgMjAyMzCCAiIwDQYJKoZIhvcN -AQEBBQADggIPADCCAgoCggIBAK7/CVmRgApKaOYkP7in5Mg6CjoWzckjYaCTcfKr -i3OPoGdlYNJUa2NRb0kz4HIHE304zQaSBylSa053bATTlfrdTIzZXcFhfUvnKLNE -gXtRr90zsWh81k5M/itoucpmacTsXld/9w3HnDY25QdgrMBM6ghs7wZ8T1soegj8 -k12b9py0i4a6Ibn08OhZWiihNIQaJZG2tY/vsvmA+vk9PBFy2OMvhnbFeSzBqZCT -Rphny4NqoFAjpzv2gTng7fC5v2Xx2Mt6++9zA84A9H3X4F07ZrjcjrqDy4d2A/wl -2ecjbwb9Z/Pg/4S8R7+1FhhGaRTMBffb00msa8yr5LULQyReS2tNZ9/WtT5PeB+U -cSTq3nD88ZP+npNa5JRal1QMNXtfbO4AHyTsA7oC9Xb0n9Sa7YUsOCIvx9gvdhFP -/Wxc6PWOJ4d/GUohR5AdeY0cW/jPSoXk7bNbjb7EZChdQcRurDhaTyN0dKkSw/bS -uREVMweR2Ds3OmMwBtHFIjYoYiMQ4EbMl6zWK11kJNXuHA7e+whadSr2Y23OC0K+ -0bpwHJwh5Q8xaRfX/Aq03u2AnMuStIv13lmiWAmlY0cL4UEyNEHZmrHZqLAbWt4N -DfTisl01gLmB1IRpkQLLddCNxbU9CZEJjxShFHR5PtbJFR2kWVki3PaKRT08EtY+ -XTIvAgMBAAGjgY4wgYswDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUZ5Dw1t61 -GNVGKX5cq/ieCLxklRAwDgYDVR0PAQH/BAQDAgEGMEkGA1UdHwRCMEAwPqA8oDqG -OGh0dHA6Ly9jcmwuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3RfYnJfcm9vdF9jYV8y -XzIwMjMuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQA097N3U9swFrktpSHxQCF16+tI -FoE9c+CeJyrrd6kTpGoKWloUMz1oH4Guaf2Mn2VsNELZLdB/eBaxOqwjMa1ef67n -riv6uvw8l5VAk1/DLQOj7aRvU9f6QA4w9QAgLABMjDu0ox+2v5Eyq6+SmNMW5tTR -VFxDWy6u71cqqLRvpO8NVhTaIasgdp4D/Ca4nj8+AybmTNudX0KEPUUDAxxZiMrc -LmEkWqTqJwtzEr5SswrPMhfiHocaFpVIbVrg0M8JkiZmkdijYQ6qgYF/6FKC0ULn -4B0Y+qSFNueG4A3rvNTJ1jxD8V1Jbn6Bm2m1iWKPiFLY1/4nwSPFyysCu7Ff/vtD -hQNGvl3GyiEm/9cCnnRK3PgTFbGBVzbLZVzRHTF36SXDw7IyN9XxmAnkbWOACKsG -koHU6XCPpz+y7YaMgmo1yEJagtFSGkUPFaUA8JR7ZSdXOUPPfH/mvTWze/EZTN46 -ls/pdu4D58JDUjxqgejBWoC9EV2Ta/vH5mQ/u2kc6d0li690yVRAysuTEwrt+2aS -Ecr1wPrYg1UDfNPFIkZ1cGt5SAYqgpq/5usWDiJFAbzdNpQ0qTUmiteXue4Icr80 -knCDgKs4qllo3UCkGJCy89UDyibK79XH4I9TjvAA46jtn/mtd+ArY0+ew+43u3gJ -hJ65bvspmZDogNOfJA== ------END CERTIFICATE----- - -# Issuer: CN=TrustAsia TLS ECC Root CA O=TrustAsia Technologies, Inc. -# Subject: CN=TrustAsia TLS ECC Root CA O=TrustAsia Technologies, Inc. -# Label: "TrustAsia TLS ECC Root CA" -# Serial: 310892014698942880364840003424242768478804666567 -# MD5 Fingerprint: 09:48:04:77:d2:fc:65:93:71:66:b1:11:95:4f:06:8c -# SHA1 Fingerprint: b5:ec:39:f3:a1:66:37:ae:c3:05:94:57:e2:be:11:be:b7:a1:7f:36 -# SHA256 Fingerprint: c0:07:6b:9e:f0:53:1f:b1:a6:56:d6:7c:4e:be:97:cd:5d:ba:a4:1e:f4:45:98:ac:c2:48:98:78:c9:2d:87:11 ------BEGIN CERTIFICATE----- -MIICMTCCAbegAwIBAgIUNnThTXxlE8msg1UloD5Sfi9QaMcwCgYIKoZIzj0EAwMw -WDELMAkGA1UEBhMCQ04xJTAjBgNVBAoTHFRydXN0QXNpYSBUZWNobm9sb2dpZXMs -IEluYy4xIjAgBgNVBAMTGVRydXN0QXNpYSBUTFMgRUNDIFJvb3QgQ0EwHhcNMjQw -NTE1MDU0MTU2WhcNNDQwNTE1MDU0MTU1WjBYMQswCQYDVQQGEwJDTjElMCMGA1UE -ChMcVHJ1c3RBc2lhIFRlY2hub2xvZ2llcywgSW5jLjEiMCAGA1UEAxMZVHJ1c3RB -c2lhIFRMUyBFQ0MgUm9vdCBDQTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLh/pVs/ -AT598IhtrimY4ZtcU5nb9wj/1WrgjstEpvDBjL1P1M7UiFPoXlfXTr4sP/MSpwDp -guMqWzJ8S5sUKZ74LYO1644xST0mYekdcouJtgq7nDM1D9rs3qlKH8kzsaNCMEAw -DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQULIVTu7FDzTLqnqOH/qKYqKaT6RAw -DgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2gAMGUCMFRH18MtYYZI9HlaVQ01 -L18N9mdsd0AaRuf4aFtOJx24mH1/k78ITcTaRTChD15KeAIxAKORh/IRM4PDwYqR -OkwrULG9IpRdNYlzg8WbGf60oenUoWa2AaU2+dhoYSi3dOGiMQ== ------END CERTIFICATE----- - -# Issuer: CN=TrustAsia TLS RSA Root CA O=TrustAsia Technologies, Inc. -# Subject: CN=TrustAsia TLS RSA Root CA O=TrustAsia Technologies, Inc. -# Label: "TrustAsia TLS RSA Root CA" -# Serial: 160405846464868906657516898462547310235378010780 -# MD5 Fingerprint: 3b:9e:c3:86:0f:34:3c:6b:c5:46:c4:8e:1d:e7:19:12 -# SHA1 Fingerprint: a5:46:50:c5:62:ea:95:9a:1a:a7:04:6f:17:58:c7:29:53:3d:03:fa -# SHA256 Fingerprint: 06:c0:8d:7d:af:d8:76:97:1e:b1:12:4f:e6:7f:84:7e:c0:c7:a1:58:d3:ea:53:cb:e9:40:e2:ea:97:91:f4:c3 ------BEGIN CERTIFICATE----- -MIIFgDCCA2igAwIBAgIUHBjYz+VTPyI1RlNUJDxsR9FcSpwwDQYJKoZIhvcNAQEM -BQAwWDELMAkGA1UEBhMCQ04xJTAjBgNVBAoTHFRydXN0QXNpYSBUZWNobm9sb2dp -ZXMsIEluYy4xIjAgBgNVBAMTGVRydXN0QXNpYSBUTFMgUlNBIFJvb3QgQ0EwHhcN -MjQwNTE1MDU0MTU3WhcNNDQwNTE1MDU0MTU2WjBYMQswCQYDVQQGEwJDTjElMCMG -A1UEChMcVHJ1c3RBc2lhIFRlY2hub2xvZ2llcywgSW5jLjEiMCAGA1UEAxMZVHJ1 -c3RBc2lhIFRMUyBSU0EgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC -AgoCggIBAMMWuBtqpERz5dZO9LnPWwvB0ZqB9WOwj0PBuwhaGnrhB3YmH49pVr7+ -NmDQDIPNlOrnxS1cLwUWAp4KqC/lYCZUlviYQB2srp10Zy9U+5RjmOMmSoPGlbYJ -Q1DNDX3eRA5gEk9bNb2/mThtfWza4mhzH/kxpRkQcwUqwzIZheo0qt1CHjCNP561 -HmHVb70AcnKtEj+qpklz8oYVlQwQX1Fkzv93uMltrOXVmPGZLmzjyUT5tUMnCE32 -ft5EebuyjBza00tsLtbDeLdM1aTk2tyKjg7/D8OmYCYozza/+lcK7Fs/6TAWe8Tb -xNRkoDD75f0dcZLdKY9BWN4ArTr9PXwaqLEX8E40eFgl1oUh63kd0Nyrz2I8sMeX -i9bQn9P+PN7F4/w6g3CEIR0JwqH8uyghZVNgepBtljhb//HXeltt08lwSUq6HTrQ -UNoyIBnkiz/r1RYmNzz7dZ6wB3C4FGB33PYPXFIKvF1tjVEK2sUYyJtt3LCDs3+j -TnhMmCWr8n4uIF6CFabW2I+s5c0yhsj55NqJ4js+k8UTav/H9xj8Z7XvGCxUq0DT -bE3txci3OE9kxJRMT6DNrqXGJyV1J23G2pyOsAWZ1SgRxSHUuPzHlqtKZFlhaxP8 -S8ySpg+kUb8OWJDZgoM5pl+z+m6Ss80zDoWo8SnTq1mt1tve1CuBAgMBAAGjQjBA -MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFLgHkXlcBvRG/XtZylomkadFK/hT -MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQwFAAOCAgEAIZtqBSBdGBanEqT3 -Rz/NyjuujsCCztxIJXgXbODgcMTWltnZ9r96nBO7U5WS/8+S4PPFJzVXqDuiGev4 -iqME3mmL5Dw8veWv0BIb5Ylrc5tvJQJLkIKvQMKtuppgJFqBTQUYo+IzeXoLH5Pt -7DlK9RME7I10nYEKqG/odv6LTytpEoYKNDbdgptvT+Bz3Ul/KD7JO6NXBNiT2Twp -2xIQaOHEibgGIOcberyxk2GaGUARtWqFVwHxtlotJnMnlvm5P1vQiJ3koP26TpUJ -g3933FEFlJ0gcXax7PqJtZwuhfG5WyRasQmr2soaB82G39tp27RIGAAtvKLEiUUj -pQ7hRGU+isFqMB3iYPg6qocJQrmBktwliJiJ8Xw18WLK7nn4GS/+X/jbh87qqA8M -pugLoDzga5SYnH+tBuYc6kIQX+ImFTw3OffXvO645e8D7r0i+yiGNFjEWn9hongP -XvPKnbwbPKfILfanIhHKA9jnZwqKDss1jjQ52MjqjZ9k4DewbNfFj8GQYSbbJIwe -SsCI3zWQzj8C9GRh3sfIB5XeMhg6j6JCQCTl1jNdfK7vsU1P1FeQNWrcrgSXSYk0 -ly4wBOeY99sLAZDBHwo/+ML+TvrbmnNzFrwFuHnYWa8G5z9nODmxfKuU4CkUpijy -323imttUQ/hHWKNddBWcwauwxzQ= ------END CERTIFICATE----- - -# Issuer: CN=D-TRUST EV Root CA 2 2023 O=D-Trust GmbH -# Subject: CN=D-TRUST EV Root CA 2 2023 O=D-Trust GmbH -# Label: "D-TRUST EV Root CA 2 2023" -# Serial: 139766439402180512324132425437959641711 -# MD5 Fingerprint: 96:b4:78:09:f0:09:cb:77:eb:bb:1b:4d:6f:36:bc:b6 -# SHA1 Fingerprint: a5:5b:d8:47:6c:8f:19:f7:4c:f4:6d:6b:b6:c2:79:82:22:df:54:8b -# SHA256 Fingerprint: 8e:82:21:b2:e7:d4:00:78:36:a1:67:2f:0d:cc:29:9c:33:bc:07:d3:16:f1:32:fa:1a:20:6d:58:71:50:f1:ce ------BEGIN CERTIFICATE----- -MIIFqTCCA5GgAwIBAgIQaSYJfoBLTKCnjHhiU19abzANBgkqhkiG9w0BAQ0FADBI -MQswCQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlE -LVRSVVNUIEVWIFJvb3QgQ0EgMiAyMDIzMB4XDTIzMDUwOTA5MTAzM1oXDTM4MDUw -OTA5MTAzMlowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEi -MCAGA1UEAxMZRC1UUlVTVCBFViBSb290IENBIDIgMjAyMzCCAiIwDQYJKoZIhvcN -AQEBBQADggIPADCCAgoCggIBANiOo4mAC7JXUtypU0w3uX9jFxPvp1sjW2l1sJkK -F8GLxNuo4MwxusLyzV3pt/gdr2rElYfXR8mV2IIEUD2BCP/kPbOx1sWy/YgJ25yE -7CUXFId/MHibaljJtnMoPDT3mfd/06b4HEV8rSyMlD/YZxBTfiLNTiVR8CUkNRFe -EMbsh2aJgWi6zCudR3Mfvc2RpHJqnKIbGKBv7FD0fUDCqDDPvXPIEysQEx6Lmqg6 -lHPTGGkKSv/BAQP/eX+1SH977ugpbzZMlWGG2Pmic4ruri+W7mjNPU0oQvlFKzIb -RlUWaqZLKfm7lVa/Rh3sHZMdwGWyH6FDrlaeoLGPaxK3YG14C8qKXO0elg6DpkiV -jTujIcSuWMYAsoS0I6SWhjW42J7YrDRJmGOVxcttSEfi8i4YHtAxq9107PncjLgc -jmgjutDzUNzPZY9zOjLHfP7KgiJPvo5iR2blzYfi6NUPGJ/lBHJLRjwQ8kTCZFZx -TnXonMkmdMV9WdEKWw9t/p51HBjGGjp82A0EzM23RWV6sY+4roRIPrN6TagD4uJ+ -ARZZaBhDM7DS3LAaQzXupdqpRlyuhoFBAUp0JuyfBr/CBTdkdXgpaP3F9ev+R/nk -hbDhezGdpn9yo7nELC7MmVcOIQxFAZRl62UJxmMiCzNJkkg8/M3OsD6Onov4/knF -NXJHAgMBAAGjgY4wgYswDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUqvyREBuH -kV8Wub9PS5FeAByxMoAwDgYDVR0PAQH/BAQDAgEGMEkGA1UdHwRCMEAwPqA8oDqG -OGh0dHA6Ly9jcmwuZC10cnVzdC5uZXQvY3JsL2QtdHJ1c3RfZXZfcm9vdF9jYV8y -XzIwMjMuY3JsMA0GCSqGSIb3DQEBDQUAA4ICAQCTy6UfmRHsmg1fLBWTxj++EI14 -QvBukEdHjqOSMo1wj/Zbjb6JzkcBahsgIIlbyIIQbODnmaprxiqgYzWRaoUlrRc4 -pZt+UPJ26oUFKidBK7GB0aL2QHWpDsvxVUjY7NHss+jOFKE17MJeNRqrphYBBo7q -3C+jisosketSjl8MmxfPy3MHGcRqwnNU73xDUmPBEcrCRbH0O1P1aa4846XerOhU -t7KR/aypH/KH5BfGSah82ApB9PI+53c0BFLd6IHyTS9URZ0V4U/M5d40VxDJI3IX -cI1QcB9WbMy5/zpaT2N6w25lBx2Eof+pDGOJbbJAiDnXH3dotfyc1dZnaVuodNv8 -ifYbMvekJKZ2t0dT741Jj6m2g1qllpBFYfXeA08mD6iL8AOWsKwV0HFaanuU5nCT -2vFp4LJiTZ6P/4mdm13NRemUAiKN4DV/6PEEeXFsVIP4M7kFMhtYVRFP0OUnR3Hs -7dpn1mKmS00PaaLJvOwiS5THaJQXfuKOKD62xur1NGyfN4gHONuGcfrNlUhDbqNP -gofXNJhuS5N5YHVpD/Aa1VP6IQzCP+k/HxiMkl14p3ZnGbuy6n/pcAlWVqOwDAst -Nl7F6cTVg8uGF5csbBNvh1qvSaYd2804BC5f4ko1Di1L+KIkBI3Y4WNeApI02phh -XBxvWHZks/wCuPWdCg== ------END CERTIFICATE----- - -# Issuer: CN=SwissSign RSA TLS Root CA 2022 - 1 O=SwissSign AG -# Subject: CN=SwissSign RSA TLS Root CA 2022 - 1 O=SwissSign AG -# Label: "SwissSign RSA TLS Root CA 2022 - 1" -# Serial: 388078645722908516278762308316089881486363258315 -# MD5 Fingerprint: 16:2e:e4:19:76:81:85:ba:8e:91:58:f1:15:ef:72:39 -# SHA1 Fingerprint: 81:34:0a:be:4c:cd:ce:cc:e7:7d:cc:8a:d4:57:e2:45:a0:77:5d:ce -# SHA256 Fingerprint: 19:31:44:f4:31:e0:fd:db:74:07:17:d4:de:92:6a:57:11:33:88:4b:43:60:d3:0e:27:29:13:cb:e6:60:ce:41 ------BEGIN CERTIFICATE----- -MIIFkzCCA3ugAwIBAgIUQ/oMX04bgBhE79G0TzUfRPSA7cswDQYJKoZIhvcNAQEL -BQAwUTELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzErMCkGA1UE -AxMiU3dpc3NTaWduIFJTQSBUTFMgUm9vdCBDQSAyMDIyIC0gMTAeFw0yMjA2MDgx -MTA4MjJaFw00NzA2MDgxMTA4MjJaMFExCzAJBgNVBAYTAkNIMRUwEwYDVQQKEwxT -d2lzc1NpZ24gQUcxKzApBgNVBAMTIlN3aXNzU2lnbiBSU0EgVExTIFJvb3QgQ0Eg -MjAyMiAtIDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDLKmjiC8NX -vDVjvHClO/OMPE5Xlm7DTjak9gLKHqquuN6orx122ro10JFwB9+zBvKK8i5VUXu7 -LCTLf5ImgKO0lPaCoaTo+nUdWfMHamFk4saMla+ju45vVs9xzF6BYQ1t8qsCLqSX -5XH8irCRIFucdFJtrhUnWXjyCcplDn/L9Ovn3KlMd/YrFgSVrpxxpT8q2kFC5zyE -EPThPYxr4iuRR1VPuFa+Rd4iUU1OKNlfGUEGjw5NBuBwQCMBauTLE5tzrE0USJIt -/m2n+IdreXXhvhCxqohAWVTXz8TQm0SzOGlkjIHRI36qOTw7D59Ke4LKa2/KIj4x -0LDQKhySio/YGZxH5D4MucLNvkEM+KRHBdvBFzA4OmnczcNpI/2aDwLOEGrOyvi5 -KaM2iYauC8BPY7kGWUleDsFpswrzd34unYyzJ5jSmY0lpx+Gs6ZUcDj8fV3oT4MM -0ZPlEuRU2j7yrTrePjxF8CgPBrnh25d7mUWe3f6VWQQvdT/TromZhqwUtKiE+shd -OxtYk8EXlFXIC+OCeYSf8wCENO7cMdWP8vpPlkwGqnj73mSiI80fPsWMvDdUDrta -clXvyFu1cvh43zcgTFeRc5JzrBh3Q4IgaezprClG5QtO+DdziZaKHG29777YtvTK -wP1H8K4LWCDFyB02rpeNUIMmJCn3nTsPBQIDAQABo2MwYTAPBgNVHRMBAf8EBTAD -AQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBRvjmKLk0Ow4UD2p8P98Q+4 -DxU4pTAdBgNVHQ4EFgQUb45ii5NDsOFA9qfD/fEPuA8VOKUwDQYJKoZIhvcNAQEL -BQADggIBAKwsKUF9+lz1GpUYvyypiqkkVHX1uECry6gkUSsYP2OprphWKwVDIqO3 -10aewCoSPY6WlkDfDDOLazeROpW7OSltwAJsipQLBwJNGD77+3v1dj2b9l4wBlgz -Hqp41eZUBDqyggmNzhYzWUUo8aWjlw5DI/0LIICQ/+Mmz7hkkeUFjxOgdg3XNwwQ -iJb0Pr6VvfHDffCjw3lHC1ySFWPtUnWK50Zpy1FVCypM9fJkT6lc/2cyjlUtMoIc -gC9qkfjLvH4YoiaoLqNTKIftV+Vlek4ASltOU8liNr3CjlvrzG4ngRhZi0Rjn9UM -ZfQpZX+RLOV/fuiJz48gy20HQhFRJjKKLjpHE7iNvUcNCfAWpO2Whi4Z2L6MOuhF -LhG6rlrnub+xzI/goP+4s9GFe3lmozm1O2bYQL7Pt2eLSMkZJVX8vY3PXtpOpvJp -zv1/THfQwUY1mFwjmwJFQ5Ra3bxHrSL+ul4vkSkphnsh3m5kt8sNjzdbowhq6/Td -Ao9QAwKxuDdollDruF/UKIqlIgyKhPBZLtU30WHlQnNYKoH3dtvi4k0NX/a3vgW0 -rk4N3hY9A4GzJl5LuEsAz/+MF7psYC0nhzck5npgL7XTgwSqT0N1osGDsieYK7EO -gLrAhV5Cud+xYJHT6xh+cHiudoO+cVrQkOPKwRYlZ0rwtnu64ZzZ ------END CERTIFICATE----- diff --git a/python/tk-core/requirements/3.7/frozen_requirements.txt b/python/tk-core/requirements/3.7/frozen_requirements.txt deleted file mode 100644 index fa55883f..00000000 --- a/python/tk-core/requirements/3.7/frozen_requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -distro==1.4.0 -packaging==24.0 -PyYAML==5.4.1 -ruamel.yaml==0.18.13 -ruamel.yaml.clib==0.2.8 -shotgun-api3==3.9.0 diff --git a/python/tk-core/requirements/3.7/pkgs.zip b/python/tk-core/requirements/3.7/pkgs.zip deleted file mode 100644 index 42f30929..00000000 Binary files a/python/tk-core/requirements/3.7/pkgs.zip and /dev/null differ diff --git a/python/tk-core/requirements/3.7/requirements.txt b/python/tk-core/requirements/3.7/requirements.txt deleted file mode 100644 index e4f56efe..00000000 --- a/python/tk-core/requirements/3.7/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -distro==1.4.0 -packaging==24.0 -pyyaml==5.4.1 -ruamel.yaml==0.18.13 -shotgun_api3==3.9.0 diff --git a/python/tk-core/requirements/3.9/frozen_requirements.txt b/python/tk-core/requirements/3.9/frozen_requirements.txt index 212246db..f7a436c5 100644 --- a/python/tk-core/requirements/3.9/frozen_requirements.txt +++ b/python/tk-core/requirements/3.9/frozen_requirements.txt @@ -1,5 +1,7 @@ distro==1.4.0 +Fileseq==2.1.2 packaging==25.0 +PyJWT==2.12.0 PyYAML==5.4.1 ruamel.yaml==0.18.14 ruamel.yaml.clib==0.2.15 diff --git a/python/tk-core/requirements/3.9/pkgs.zip b/python/tk-core/requirements/3.9/pkgs.zip index c0043ab1..86e8258b 100644 Binary files a/python/tk-core/requirements/3.9/pkgs.zip and b/python/tk-core/requirements/3.9/pkgs.zip differ diff --git a/python/tk-core/requirements/3.9/requirements.txt b/python/tk-core/requirements/3.9/requirements.txt index 4db18269..24a23f3c 100644 --- a/python/tk-core/requirements/3.9/requirements.txt +++ b/python/tk-core/requirements/3.9/requirements.txt @@ -1,5 +1,7 @@ distro==1.4.0 +fileseq==2.1.2 packaging==25.0 +pyjwt==2.12.0 pyyaml==5.4.1 ruamel.yaml==0.18.14 shotgun_api3==3.10.0 diff --git a/python/tk-core/requirements/any/flow_data_sdk-beta.zip b/python/tk-core/requirements/any/flow_data_sdk-beta.zip new file mode 100644 index 00000000..0250e7fe Binary files /dev/null and b/python/tk-core/requirements/any/flow_data_sdk-beta.zip differ diff --git a/python/tk-core/requirements/update_python_packages.py b/python/tk-core/requirements/update_python_packages.py index 77c5e684..abfabba5 100644 --- a/python/tk-core/requirements/update_python_packages.py +++ b/python/tk-core/requirements/update_python_packages.py @@ -88,9 +88,14 @@ def main(): ] # Make sure we found as many Python packages as there - # are packages listed inside frozen_requirements.txt - # assert len(package_names) == nb_dependencies - assert len(package_names) >= nb_dependencies + # are packages listed inside frozen_requirements.txt. + # Count .dist-info directories rather than package directories because + # namespace packages (e.g. ruamel.yaml + ruamel.yaml.clib, jaraco.*) + # share a single parent directory, making package_names < nb_dependencies. + dist_info_count = len( + [name for name in os.listdir(temp_dir) if name.endswith(".dist-info")] + ) + assert dist_info_count >= nb_dependencies # Write out the zip file for python packages. Compress the zip file with ZIP_DEFLATED. Note # that this requires zlib to decompress when importing. Compression also causes import to diff --git a/python/tk-core/scripts/tank_cmd.py b/python/tk-core/scripts/tank_cmd.py index e686eea4..b43e0dbe 100644 --- a/python/tk-core/scripts/tank_cmd.py +++ b/python/tk-core/scripts/tank_cmd.py @@ -8,36 +8,36 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -import sys -import os -from html import escape +import datetime import logging +import os import string -import tank +import sys import textwrap -import datetime +from html import escape -from tank.errors import TankError, TankInitError -from tank.commands.tank_command import get_actions, run_action +import tank +from tank import LogManager, pipelineconfig_utils +from tank.authentication import ( + AuthenticationCancelled, + AuthenticationError, + CoreDefaultsManager, + IncompleteCredentials, + ShotgunAuthenticationError, + ShotgunAuthenticator, +) +from tank.commands import constants as command_constants +from tank.commands.action_base import Action from tank.commands.clone_configuration import clone_pipeline_configuration_html from tank.commands.core_upgrade import TankCoreUpdater -from tank.commands.action_base import Action -from tank.util import shotgun -from tank.util import shotgun_entity +from tank.commands.tank_command import get_actions, run_action +from tank.errors import TankError, TankInitError +from tank.platform import constants as platform_constants +from tank.platform import engine from tank.util import is_windows from tank.util import sgre as re -from tank.platform import constants as platform_constants -from tank.authentication import ShotgunAuthenticator -from tank.authentication import AuthenticationError -from tank.authentication import ShotgunAuthenticationError -from tank.authentication import AuthenticationCancelled -from tank.authentication import IncompleteCredentials -from tank.authentication import CoreDefaultsManager -from tank.commands import constants as command_constants +from tank.util import shotgun, shotgun_entity from tank_vendor import yaml -from tank.platform import engine -from tank import pipelineconfig_utils -from tank import LogManager # the logger used by this file is sgtk.tank_cmd logger = LogManager.get_logger("tank_cmd") diff --git a/python/tk-core/setup.py b/python/tk-core/setup.py index 43b4f24a..01dab5fe 100644 --- a/python/tk-core/setup.py +++ b/python/tk-core/setup.py @@ -10,12 +10,32 @@ # Basic setup.py so tk-core could be installed as # a standard Python package +import glob import os import re +import shutil import subprocess import sys -from setuptools import setup, find_packages +from setuptools import find_packages, setup +from setuptools.command.build_py import build_py as _build_py + + +class build_py(_build_py): + def run(self): + super().run() + # Copy requirements/any/*.zip into tank_vendor/vendor_any/ inside the + # build tree so pip-installed packages can find version-independent + # vendor zips (e.g. flow_data_sdk). Editable installs skip this step + # intentionally — they use the source-tree requirements/any/ path. + src_dir = os.path.join( + os.path.dirname(os.path.abspath(__file__)), "requirements", "any" + ) + dst_dir = os.path.join(self.build_lib, "tank_vendor", "vendor_any") + if os.path.isdir(src_dir): + os.makedirs(dst_dir, exist_ok=True) + for zip_path in glob.glob(os.path.join(src_dir, "*.zip")): + shutil.copy2(zip_path, dst_dir) def get_version(): @@ -40,7 +60,7 @@ def get_version(): if re.match(r"v[0-9]*.[0-9]*.[0-9]*", version_git): return version_git return "dev" - except: + except Exception: # Blindly ignore problems, git might be not available, or the user could # be installing from zip archive, etc... pass @@ -97,6 +117,7 @@ def get_install_requires(): f.close() setup( + cmdclass={"build_py": build_py}, name="sgtk", version=get_version(), description="Flow Production Tracking Toolkit Core API", diff --git a/python/tk-core/setup/tank_api_proxy/sgtk/__init__.py b/python/tk-core/setup/tank_api_proxy/sgtk/__init__.py index d0c107d4..1ac7cd76 100644 --- a/python/tk-core/setup/tank_api_proxy/sgtk/__init__.py +++ b/python/tk-core/setup/tank_api_proxy/sgtk/__init__.py @@ -11,6 +11,7 @@ # thin proxy wrapper which finds the real sgtk and replaces itself with that +import importlib import os import sys @@ -63,4 +64,4 @@ # prepend this to the python path and reload the module # this way we will load the 'real' tank! os.sys.path.insert(0, parent_python_path) -reload(sys.modules["sgtk"]) +importlib.reload(sys.modules["sgtk"]) diff --git a/python/tk-core/setup/tank_api_proxy/tank/__init__.py b/python/tk-core/setup/tank_api_proxy/tank/__init__.py index a02f7590..15ad0b7c 100644 --- a/python/tk-core/setup/tank_api_proxy/tank/__init__.py +++ b/python/tk-core/setup/tank_api_proxy/tank/__init__.py @@ -11,6 +11,7 @@ # thin proxy wrapper which finds the real tank and replaces itself with that +import importlib import os import sys @@ -62,4 +63,4 @@ # prepend this to the python path and reload the module # this way we will load the 'real' tank! os.sys.path.insert(0, parent_python_path) -reload(sys.modules["tank"]) +importlib.reload(sys.modules["tank"])