From 05b6621e6137b9fcae2dc457c3020418b1a740c2 Mon Sep 17 00:00:00 2001 From: Ariel Calzada Date: Tue, 23 Nov 2021 22:54:35 -0500 Subject: [PATCH 001/125] SG-21170-ROUND2 [ZD to AKN] Update links referenced from code to the old support site to point to AKN (#254) * Replace ZD urls to AKN and developer urls * Update URLs --- docs/authentication.rst | 2 +- docs/cookbook.rst | 2 +- docs/cookbook/examples/ami_handler.rst | 2 +- docs/cookbook/examples/ami_version_packager.rst | 2 +- docs/cookbook/smart_cut_fields.rst | 2 +- docs/cookbook/tasks/task_dependencies.rst | 2 +- docs/reference.rst | 2 +- setup.py | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/authentication.rst b/docs/authentication.rst index 7708ad026..1e77ee279 100644 --- a/docs/authentication.rst +++ b/docs/authentication.rst @@ -61,5 +61,5 @@ For Scripts, the default permission role is "API Admin User" which allows full a When using user-based authentication in your script, it will be bound by the permission role assigned to you in Shotgun. For example, if you don't have access to edit the status field on Shots, your script won't be able to either. Attempting to perform actions that are prohibited by permissions will raise an appropriate exception. -.. seealso:: `Shotgun Support: Permissions `_ +.. seealso:: `Shotgun Support: Permissions `_ diff --git a/docs/cookbook.rst b/docs/cookbook.rst index 5c4c1be78..54f4d983f 100644 --- a/docs/cookbook.rst +++ b/docs/cookbook.rst @@ -52,7 +52,7 @@ need to do. .. rubric:: Smart Cut Fields Smart Cut Fields are deprecated in favor of the -`new cut support added in Shotgun v7.0 `_. +`new cut support added in Shotgun v7.0 `_. This documentation remains only to support studios who may not have upgraded to the new cut support features. diff --git a/docs/cookbook/examples/ami_handler.rst b/docs/cookbook/examples/ami_handler.rst index d4b88f006..ebd666866 100644 --- a/docs/cookbook/examples/ami_handler.rst +++ b/docs/cookbook/examples/ami_handler.rst @@ -13,7 +13,7 @@ Available as a Gist at https://gist.github.com/3253287 .. seealso:: Our `support site has more information about Action Menu Items - `_. + `_. ************ GET vs. POST diff --git a/docs/cookbook/examples/ami_version_packager.rst b/docs/cookbook/examples/ami_version_packager.rst index 67e983a2a..445a38d93 100644 --- a/docs/cookbook/examples/ami_version_packager.rst +++ b/docs/cookbook/examples/ami_version_packager.rst @@ -23,7 +23,7 @@ It is intended to be used in conjunction with the script dicussed in :ref:`ami_h This example script is meant to be run from an ActionMenuItem in Shotgun. The menu item uses a custom protocol in order to launch this script, and is followed by the action 'package4client'. So the full url would be something like launchme://package4client?.... See: - http://support.shotgunsoftware.com/hc/en-us/articles/219031318-Creating-custom-Action-Menu-Items + https://developer.shotgridsoftware.com/python-api/cookbook/examples/ami_handler.html It uses the example ActionMenu Python class also located in our docs for parsing the ActionMenuItem POST variables. For more information about it and accessing the variables in the ActionMenuItem POST request, diff --git a/docs/cookbook/smart_cut_fields.rst b/docs/cookbook/smart_cut_fields.rst index 9dd7fcae1..8e47f87d9 100644 --- a/docs/cookbook/smart_cut_fields.rst +++ b/docs/cookbook/smart_cut_fields.rst @@ -6,7 +6,7 @@ Smart Cut Fields .. warning:: Smart Cut Fields should be considered deprecated. Shotgun v7.0, introduced a new version of - cut support. `Read the Cut Support Documentation here `_. + cut support. `Read the Cut Support Documentation here `_. If you want to work with 'smart' cut fields through the API, your script should use a corresponding 'raw' fields for all updates. The 'smart_cut_fields' are primarily for display in the UI, the real diff --git a/docs/cookbook/tasks/task_dependencies.rst b/docs/cookbook/tasks/task_dependencies.rst index 0b63d3255..b16c32ac3 100644 --- a/docs/cookbook/tasks/task_dependencies.rst +++ b/docs/cookbook/tasks/task_dependencies.rst @@ -7,7 +7,7 @@ Task Dependencies Task dependencies work the same way in the API as they do in the UI. You can filter and sort on any of the fields. For information about Task Dependencies in Shotgun, check out the `main documentation page on our support site -`_ +`_ ************ Create Tasks diff --git a/docs/reference.rst b/docs/reference.rst index 4fb6748e2..8ef3bf704 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -902,7 +902,7 @@ usage. This **does not** mean your Shotgun server performance will suffer in general, just any pages that are specifically displaying EventLogEntries in the web application, or API queries on the event log that are run. We are always looking for ways to improve this in the future. If you have any -immediate concerns, please `reach out to our support team `_ +immediate concerns, please `reach out to our support team `_ ********************* Environment Variables diff --git a/setup.py b/setup.py index 4e5f74b41..02696d523 100644 --- a/setup.py +++ b/setup.py @@ -31,7 +31,7 @@ description='Shotgun Python API ', long_description=readme, author='Shotgun Software', - author_email='https://support.shotgunsoftware.com', + author_email='https://developer.shotgridsoftware.com', url='https://github.com/shotgunsoftware/python-api', license=license, packages=find_packages(exclude=('tests',)), From af15c826d736ee9c683c400bd945707f0ca5b14f Mon Sep 17 00:00:00 2001 From: Ariel Calzada Date: Mon, 29 Nov 2021 16:21:14 -0500 Subject: [PATCH 002/125] SG-21170-ROUND3 [ZD to AKN] Update links referenced from code to the old support site to point to AKN (#255) * Replace ZD urls to AKN and developer urls * Adjust support url --- README.md | 2 +- docs/advanced.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 55337f2cb..8baae0cfb 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ # Shotgun Python API -ShotGrid provides a simple Python-based API for accessing ShotGrid and integrating with other tools. This is the official API that is maintained by ShotGrid Software (support@shotgunsoftware.com) +ShotGrid provides a simple Python-based API for accessing ShotGrid and integrating with other tools. This is the official API that is maintained by ShotGrid Software (https://knowledge.autodesk.com/contact-support) The latest version can always be found at http://github.com/shotgunsoftware/python-api diff --git a/docs/advanced.rst b/docs/advanced.rst index 4c7e4a2bf..f10437ff5 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -5,7 +5,7 @@ Advanced Topics ############### Below are some more advanced topics regarding usage of the Python API. If you would like to see -something that's missing here, please feel free to contact support at support@shotgunsoftware.com +something that's missing here, please feel free to contact support at https://knowledge.autodesk.com/contact-support with your suggestions and we'll get it added! .. toctree:: From b9f066c0edbea6e0733242e18f32f75489064840 Mon Sep 17 00:00:00 2001 From: Ariel Calzada Date: Wed, 1 Dec 2021 14:21:10 -0500 Subject: [PATCH 003/125] SG-21170-PKG-3-3-3 [ZD to AKN] Update links referenced from code to the old support site to point to AKN #256 Packaging the changes for the v3.3.3 release (#256) --- HISTORY.rst | 4 ++++ setup.py | 2 +- shotgun_api3/shotgun.py | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 116e04d26..cf252a7b1 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -4,6 +4,10 @@ ShotGrid Python API Changelog Here you can see the full list of changes between each Python API release. +v3.3.3 (2021 December 1) +========================== +- Replaces shotgunsoftware urls with Autodesk Knowledge Network and ShotGrid Developer Documentation pages. + v3.3.2 (2021 September 27) ========================== - Updates version of httplib2. diff --git a/setup.py b/setup.py index 02696d523..e1e0f958f 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ setup( name='shotgun_api3', - version='3.3.2', + version='3.3.3', description='Shotgun Python API ', long_description=readme, author='Shotgun Software', diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index de7fccb77..acaeb06fe 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -117,7 +117,7 @@ def _is_mimetypes_broken(): # ---------------------------------------------------------------------------- # Version -__version__ = "3.3.2" +__version__ = "3.3.3" # ---------------------------------------------------------------------------- # Errors From aca5fd7a9d1d1810017ec0236d5e547c511d4cd5 Mon Sep 17 00:00:00 2001 From: Norberto Moreno Date: Tue, 4 Jan 2022 08:20:26 -0500 Subject: [PATCH 004/125] SG-24976 Update AMI Documentation To_Support Python 3 (#258) * Updating AMI documentation example to run with Python 2/3. * Linting code example. --- docs/cookbook/examples/ami_handler.rst | 114 +++++++++++++------------ 1 file changed, 59 insertions(+), 55 deletions(-) diff --git a/docs/cookbook/examples/ami_handler.rst b/docs/cookbook/examples/ami_handler.rst index ebd666866..f64ccd558 100644 --- a/docs/cookbook/examples/ami_handler.rst +++ b/docs/cookbook/examples/ami_handler.rst @@ -57,7 +57,7 @@ via ``POST``. If you're using a custom protocol the data is sent via ``GET``. 'sort_direction': 'desc', 'project_id': '4', 'session_uuid': 'd8592bd6-fc41-11e1-b2c5-000c297a5f50', - 'column_display_names': + 'column_display_names': [ 'Version Name', 'Thumbnail', @@ -71,19 +71,19 @@ via ``POST``. If you're using a custom protocol the data is sent via ``GET``. ] } - This simple class parses the url into easy to access types variables from the parameters, - action, and protocol sections of the url. This example url + This simple class parses the url into easy to access types variables from the parameters, + action, and protocol sections of the url. This example url myCoolProtocol://doSomethingCool?user_id=123&user_login=miled&title=All%20Versions&... would be parsed like this: (string) protocol: myCoolProtocol (string) action: doSomethingCool (dict) params: user_id=123&user_login=miled&title=All%20Versions&... - + The parameters variable will be returned as a dictionary of string key/value pairs. Here's how to instantiate: - sa = ShotgunAction(sys.argv[1]) # sys.argv[1] + sa = ShotgunAction(sys.argv[1]) # sys.argv[1] sa.params['user_login'] # returns 'miled' sa.params['user_id'] # returns 123 @@ -95,17 +95,15 @@ via ``POST``. If you're using a custom protocol the data is sent via ``GET``. # Imports # --------------------------------------------------------------------------------------------- import sys, os - import urllib + import six import logging as logger - from pprint import pprint - # --------------------------------------------------------------------------------------------- # Variables # --------------------------------------------------------------------------------------------- # location to write logfile for this script # logging is a bit of overkill for this class, but can still be useful. - logfile = os.path.dirname(sys.argv[0])+"/shotgun_action.log" + logfile = os.path.dirname(sys.argv[0]) + "/shotgun_action.log" # ---------------------------------------------- @@ -113,48 +111,50 @@ via ``POST``. If you're using a custom protocol the data is sent via ``GET``. # ---------------------------------------------- class ShotgunActionException(Exception): pass - + # ---------------------------------------------- # ShotgunAction Class to manage ActionMenuItem call # ---------------------------------------------- - class ShotgunAction(): - + class ShotgunAction: def __init__(self, url): self.logger = self._init_log(logfile) self.url = url - self.protocol, self.action, self.params = self._parse_url() - + self.protocol, self.action, self.params = self._parse_url() + # entity type that the page was displaying - self.entity_type = self.params['entity_type'] + self.entity_type = self.params["entity_type"] # Project info (if the ActionMenuItem was launched from a page not belonging # to a Project (Global Page, My Page, etc.), this will be blank - if 'project_id' in self.params: - self.project = { 'id':int(self.params['project_id']), 'name':self.params['project_name'] } + if "project_id" in self.params: + self.project = { + "id": int(self.params["project_id"]), + "name": self.params["project_name"], + } else: self.project = None # Internal column names currently displayed on the page - self.columns = self.params['cols'] + self.columns = self.params["cols"] # Human readable names of the columns currently displayed on the page - self.column_display_names = self.params['column_display_names'] + self.column_display_names = self.params["column_display_names"] # All ids of the entities returned by the query (not just those visible on the page) self.ids = [] - if len(self.params['ids']) > 0: - ids = self.params['ids'].split(',') + if len(self.params["ids"]) > 0: + ids = self.params["ids"].split(",") self.ids = [int(id) for id in ids] - + # All ids of the entities returned by the query in filter format ready # to use in a find() query self.ids_filter = self._convert_ids_to_filter(self.ids) # ids of entities that were currently selected self.selected_ids = [] - if len(self.params['selected_ids']) > 0: - sids = self.params['selected_ids'].split(',') + if len(self.params["selected_ids"]) > 0: + sids = self.params["selected_ids"].split(",") self.selected_ids = [int(id) for id in sids] # All selected ids of the entities returned by the query in filter format ready @@ -163,47 +163,52 @@ via ``POST``. If you're using a custom protocol the data is sent via ``GET``. # sort values for the page # (we don't allow no sort anymore, but not sure if there's legacy here) - if 'sort_column' in self.params: - self.sort = { 'column':self.params['sort_column'], 'direction':self.params['sort_direction'] } + if "sort_column" in self.params: + self.sort = { + "column": self.params["sort_column"], + "direction": self.params["sort_direction"], + } else: self.sort = None - + # title of the page - self.title = self.params['title'] + self.title = self.params["title"] # user info who launched the ActionMenuItem - self.user = { 'id':self.params['user_id'], 'login':self.params['user_login']} + self.user = {"id": self.params["user_id"], "login": self.params["user_login"]} # session_uuid - self.session_uuid = self.params['session_uuid'] + self.session_uuid = self.params["session_uuid"] # ---------------------------------------------- # Set up logging # ---------------------------------------------- - def _init_log(self, filename="shotgun_action.log"): + def _init_log(self, filename="shotgun_action.log"): try: - logger.basicConfig(level=logger.DEBUG, - format='%(asctime)s %(levelname)-8s %(message)s', - datefmt='%Y-%b-%d %H:%M:%S', - filename=filename, - filemode='w+') - except IOError, e: - raise ShotgunActionException ("Unable to open logfile for writing: %s" % e) - logger.info("ShotgunAction logging started.") - return logger - + logger.basicConfig( + level=logger.DEBUG, + format="%(asctime)s %(levelname)-8s %(message)s", + datefmt="%Y-%b-%d %H:%M:%S", + filename=filename, + filemode="w+", + ) + except IOError as e: + raise ShotgunActionException("Unable to open logfile for writing: %s" % e) + logger.info("ShotgunAction logging started.") + return logger + + # ---------------------------------------------- - # ---------------------------------------------- # Parse ActionMenuItem call into protocol, action and params # ---------------------------------------------- def _parse_url(self): - logger.info("Parsing full url received: %s" % self.url) + logger.info("Parsing full url received: %s" % self.url) - # get the protocol used + # get the protocol used protocol, path = self.url.split(":", 1) logger.info("protocol: %s" % protocol) - - # extract the action + + # extract the action action, params = path.split("?", 1) action = action.strip("/") logger.info("action: %s" % action) @@ -211,28 +216,27 @@ via ``POST``. If you're using a custom protocol the data is sent via ``GET``. # extract the parameters # 'column_display_names' and 'cols' occurs once for each column displayed so we store it as a list params = params.split("&") - p = {'column_display_names':[], 'cols':[]} + p = {"column_display_names": [], "cols": []} for arg in params: - key, value = map(urllib.unquote, arg.split("=", 1)) - if key == 'column_display_names' or key == 'cols' : + key, value = map(six.moves.urllib.parse.unquote, arg.split("=", 1)) + if key == "column_display_names" or key == "cols": p[key].append(value) else: p[key] = value params = p logger.info("params: %s" % params) return (protocol, action, params) - - + # ---------------------------------------------- # Convert IDs to filter format to us in find() queries # ---------------------------------------------- def _convert_ids_to_filter(self, ids): filter = [] for id in ids: - filter.append(['id','is',id]) + filter.append(["id", "is", id]) logger.debug("parsed ids into: %s" % filter) return filter - + # ---------------------------------------------- # Main Block @@ -240,7 +244,7 @@ via ``POST``. If you're using a custom protocol the data is sent via ``GET``. if __name__ == "__main__": try: sa = ShotgunAction(sys.argv[1]) - logger.info("ShotgunAction: Firing... %s" % (sys.argv[1]) ) - except IndexError, e: + logger.info("ShotgunAction: Firing... %s" % (sys.argv[1])) + except IndexError as e: raise ShotgunActionException("Missing GET arguments") logger.info("ShotgunAction process finished.") From 8877beff16297247c441d719f839f79fdb904959 Mon Sep 17 00:00:00 2001 From: Ariel Calzada Date: Tue, 18 Jan 2022 06:52:54 -0500 Subject: [PATCH 005/125] SG-25029 Add Python 3.9 coverage to Python API Azure Pipelines tests (#259) * Map base 64 encode method * Fix configparser compatibility * Add tests config parser, string encoding and splitting urls * Fix assertEquals warnings --- .gitignore | 2 + azure-pipelines-templates/run-tests.yml | 2 + shotgun_api3/lib/six.py | 1 + shotgun_api3/shotgun.py | 50 +++++++++++++++--- tests/base.py | 8 ++- tests/test_api.py | 10 ++-- tests/test_client.py | 67 +++++++++++++++++++++++-- tests/test_config_file | 7 +++ tests/test_unit.py | 66 ++++++++++++------------ 9 files changed, 162 insertions(+), 51 deletions(-) create mode 100644 tests/test_config_file diff --git a/.gitignore b/.gitignore index 283d4a09f..0aaeb9a64 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,5 @@ htmlcov build dist shotgun_api3.egg-info +/%1 + diff --git a/azure-pipelines-templates/run-tests.yml b/azure-pipelines-templates/run-tests.yml index 4da866081..dcf8926b2 100644 --- a/azure-pipelines-templates/run-tests.yml +++ b/azure-pipelines-templates/run-tests.yml @@ -48,6 +48,8 @@ jobs: python.version: '2.7' Python37: python.version: '3.7' + Python39: + python.version: '3.9' # These are the steps that will be executed inside each job. steps: diff --git a/shotgun_api3/lib/six.py b/shotgun_api3/lib/six.py index 357e624ab..b22d2e57d 100644 --- a/shotgun_api3/lib/six.py +++ b/shotgun_api3/lib/six.py @@ -36,6 +36,7 @@ PY2 = sys.version_info[0] == 2 PY3 = sys.version_info[0] == 3 PY34 = sys.version_info[0:2] >= (3, 4) +PY38 = sys.version_info[0:2] >= (3, 8) if PY3: string_types = str, diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index acaeb06fe..7e48573d2 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -35,7 +35,6 @@ from .lib.six import BytesIO # used for attachment upload from .lib.six.moves import map -import base64 from .lib.six.moves import http_cookiejar # used for attachment upload import datetime import logging @@ -57,6 +56,12 @@ # to be exposed as part of the API. from .lib.six.moves.xmlrpc_client import Error, ProtocolError, ResponseError # noqa +if six.PY3: + from base64 import encodebytes as base64encode +else: + from base64 import encodestring as base64encode + + LOG = logging.getLogger("shotgun_api3") """ Logging instance for shotgun_api3 @@ -651,18 +656,20 @@ def __init__(self, # if the service contains user information strip it out # copied from the xmlrpclib which turned the user:password into # and auth header - # Do NOT urlsplit(self.base_url) here, as it contains the lower case version - # of the base_url argument. Doing so would base64-encode the lowercase - # version of the credentials. - auth, self.config.server = urllib.parse.splituser(urllib.parse.urlsplit(base_url).netloc) + + # Do NOT self._split_url(self.base_url) here, as it contains the lower + # case version of the base_url argument. Doing so would base64encode + # the lowercase version of the credentials. + auth, self.config.server = self._split_url(base_url) if auth: - auth = base64.encodestring(six.ensure_binary(urllib.parse.unquote(auth))).decode("utf-8") + auth = base64encode(six.ensure_binary( + urllib.parse.unquote(auth))).decode("utf-8") self.config.authorization = "Basic " + auth.strip() # foo:bar@123.456.789.012:3456 if http_proxy: - # check if we're using authentication. Start from the end since there might be - # @ in the user's password. + # check if we're using authentication. Start from the end since + # there might be @ in the user's password. p = http_proxy.rsplit("@", 1) if len(p) > 1: self.config.proxy_user, self.config.proxy_pass = \ @@ -710,6 +717,33 @@ def __init__(self, self.config.user_password = None self.config.auth_token = None + def _split_url(self, base_url): + """ + Extract the hostname:port and username/password/token from base_url + sent when connect to the API. + + In python 3.8 `urllib.parse.splituser` was deprecated warning devs to + use `urllib.parse.urlparse`. + """ + if six.PY38: + auth = None + results = urllib.parse.urlparse(base_url) + server = results.hostname + if results.port: + server = "{}:{}".format(server, results.port) + + if results.username: + auth = results.username + + if results.password: + auth = "{}:{}".format(auth, results.password) + + else: + auth, server = urllib.parse.splituser( + urllib.parse.urlsplit(base_url).netloc) + + return auth, server + # ======================================================================== # API Functions diff --git a/tests/base.py b/tests/base.py index f1179c06c..8d0e3a5bb 100644 --- a/tests/base.py +++ b/tests/base.py @@ -2,8 +2,6 @@ import os import re import unittest -from shotgun_api3.lib.six.moves.configparser import ConfigParser - from . import mock @@ -12,6 +10,12 @@ from shotgun_api3.shotgun import ServerCapabilities from shotgun_api3.lib import six +if six.PY2: + from shotgun_api3.lib.six.moves.configparser import SafeConfigParser as ConfigParser +else: + from shotgun_api3.lib.six.moves.configparser import ConfigParser + + try: # Attempt to import skip from unittest. Since this was added in Python 2.7 # in the case that we're running on Python 2.6 we'll need a decorator to diff --git a/tests/test_api.py b/tests/test_api.py index 506322164..0e341838a 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1661,7 +1661,7 @@ def test_zero_is_not_none(self): # Should be filtered out result = self.sg.find('Asset', [['id', 'is', self.asset['id']], [num_field, 'is_not', None]], [num_field]) - self.assertEquals([], result) + self.assertEqual([], result) # Set it to zero self.sg.update('Asset', self.asset['id'], {num_field: 0}) @@ -1681,17 +1681,17 @@ def test_include_archived_projects(self): if self.sg.server_caps.version > (5, 3, 13): # Ticket #25082 result = self.sg.find_one('Shot', [['id', 'is', self.shot['id']]]) - self.assertEquals(self.shot['id'], result['id']) + self.assertEqual(self.shot['id'], result['id']) # archive project self.sg.update('Project', self.project['id'], {'archived': True}) # setting defaults to True, so we should get result result = self.sg.find_one('Shot', [['id', 'is', self.shot['id']]]) - self.assertEquals(self.shot['id'], result['id']) + self.assertEqual(self.shot['id'], result['id']) result = self.sg.find_one('Shot', [['id', 'is', self.shot['id']]], include_archived_projects=False) - self.assertEquals(None, result) + self.assertEqual(None, result) # unarchive project self.sg.update('Project', self.project['id'], {'archived': False}) @@ -2810,7 +2810,7 @@ def test_import_httplib(self): # right one.) httplib2_compat_version = httplib2.Http.__module__.split(".")[-1] if six.PY2: - self.assertEquals(httplib2_compat_version, "python2") + self.assertEqual(httplib2_compat_version, "python2") elif six.PY3: self.assertTrue(httplib2_compat_version, "python3") diff --git a/tests/test_client.py b/tests/test_client.py index 00130c5d9..db4b93bdc 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -12,11 +12,11 @@ CRUD functions. These tests always use a mock http connection so not not need a live server to run against.""" -import base64 import datetime -from shotgun_api3.lib.six.moves import urllib import os import re + +from shotgun_api3.lib.six.moves import urllib from shotgun_api3.lib import six try: import simplejson as json @@ -38,8 +38,14 @@ from . import base +if six.PY3: + from base64 import encodebytes as base64encode +else: + from base64 import encodestring as base64encode + + def b64encode(val): - return base64.encodestring(six.ensure_binary(val)).decode("utf-8") + return base64encode(six.ensure_binary(val)).decode("utf-8") class TestShotgunClient(base.MockTestBase): @@ -164,6 +170,61 @@ def test_url(self): expected = "Basic " + b64encode(urllib.parse.unquote(login_password)).strip() self.assertEqual(expected, sg.config.authorization) + def test_b64encode(self): + """Parse value using the proper encoder.""" + login = "thelogin" + password = "%thepassw0r#$" + login_password = "%s:%s" % (login, password) + expected = 'dGhlbG9naW46JXRoZXBhc3N3MHIjJA==' + result = b64encode(urllib.parse.unquote(login_password)).strip() + self.assertEqual(expected, result) + + def test_read_config(self): + """Validate that config values are properly coerced.""" + this_dir = os.path.dirname(os.path.realpath(__file__)) + config_path = os.path.join(this_dir, "test_config_file") + config = base.ConfigParser() + config.read(config_path) + result = config.get("SERVER_INFO", "api_key") + expected = "%abce" + + self.assertEqual(expected, result) + + def test_split_url(self): + """Validate that url parts are properly extracted.""" + + sg = api.Shotgun("https://ci.shotgunstudio.com", + "foo", "bar", connect=False) + + + base_url = "https://ci.shotgunstudio.com" + expected_server = "ci.shotgunstudio.com" + expected_auth = None + auth, server = sg._split_url(base_url) + self.assertEqual(auth, expected_auth) + self.assertEqual(server, expected_server) + + base_url = "https://ci.shotgunstudio.com:9500" + expected_server = "ci.shotgunstudio.com:9500" + expected_auth = None + auth, server = sg._split_url(base_url) + self.assertEqual(auth, expected_auth) + self.assertEqual(server, expected_server) + + base_url = "https://x:y@ci.shotgunstudio.com:9500" + expected_server = "ci.shotgunstudio.com:9500" + expected_auth = "x:y" + auth, server = sg._split_url(base_url) + self.assertEqual(auth, expected_auth) + self.assertEqual(server, expected_server) + + base_url = "https://12345XYZ@ci.shotgunstudio.com:9500" + expected_server = "ci.shotgunstudio.com:9500" + expected_auth = "12345XYZ" + auth, server = sg._split_url(base_url) + self.assertEqual(auth, expected_auth) + self.assertEqual(server, expected_server) + def test_authorization(self): """Authorization passed to server""" login = self.human_user['login'] diff --git a/tests/test_config_file b/tests/test_config_file new file mode 100644 index 000000000..8215eecde --- /dev/null +++ b/tests/test_config_file @@ -0,0 +1,7 @@ +[SERVER_INFO] +server_url : https://url +script_name : xyz +api_key : %%abce + +[TEST_DATA] +project_name : hjkl \ No newline at end of file diff --git a/tests/test_unit.py b/tests/test_unit.py index 23ff67e06..1755b51cb 100644 --- a/tests/test_unit.py +++ b/tests/test_unit.py @@ -35,8 +35,8 @@ def test_http_proxy_server(self): self.api_key, http_proxy=http_proxy, connect=False) - self.assertEquals(sg.config.proxy_server, proxy_server) - self.assertEquals(sg.config.proxy_port, 8080) + self.assertEqual(sg.config.proxy_server, proxy_server) + self.assertEqual(sg.config.proxy_port, 8080) proxy_server = "123.456.789.012" http_proxy = proxy_server sg = api.Shotgun(self.server_path, @@ -44,8 +44,8 @@ def test_http_proxy_server(self): self.api_key, http_proxy=http_proxy, connect=False) - self.assertEquals(sg.config.proxy_server, proxy_server) - self.assertEquals(sg.config.proxy_port, 8080) + self.assertEqual(sg.config.proxy_server, proxy_server) + self.assertEqual(sg.config.proxy_port, 8080) def test_http_proxy_server_and_port(self): proxy_server = "someserver.com" @@ -56,8 +56,8 @@ def test_http_proxy_server_and_port(self): self.api_key, http_proxy=http_proxy, connect=False) - self.assertEquals(sg.config.proxy_server, proxy_server) - self.assertEquals(sg.config.proxy_port, proxy_port) + self.assertEqual(sg.config.proxy_server, proxy_server) + self.assertEqual(sg.config.proxy_port, proxy_port) proxy_server = "123.456.789.012" proxy_port = 1234 http_proxy = "%s:%d" % (proxy_server, proxy_port) @@ -66,8 +66,8 @@ def test_http_proxy_server_and_port(self): self.api_key, http_proxy=http_proxy, connect=False) - self.assertEquals(sg.config.proxy_server, proxy_server) - self.assertEquals(sg.config.proxy_port, proxy_port) + self.assertEqual(sg.config.proxy_server, proxy_server) + self.assertEqual(sg.config.proxy_port, proxy_port) def test_http_proxy_server_and_port_with_authentication(self): proxy_server = "someserver.com" @@ -81,10 +81,10 @@ def test_http_proxy_server_and_port_with_authentication(self): self.api_key, http_proxy=http_proxy, connect=False) - self.assertEquals(sg.config.proxy_server, proxy_server) - self.assertEquals(sg.config.proxy_port, proxy_port) - self.assertEquals(sg.config.proxy_user, proxy_user) - self.assertEquals(sg.config.proxy_pass, proxy_pass) + self.assertEqual(sg.config.proxy_server, proxy_server) + self.assertEqual(sg.config.proxy_port, proxy_port) + self.assertEqual(sg.config.proxy_user, proxy_user) + self.assertEqual(sg.config.proxy_pass, proxy_pass) proxy_server = "123.456.789.012" proxy_port = 1234 proxy_user = "user" @@ -96,10 +96,10 @@ def test_http_proxy_server_and_port_with_authentication(self): self.api_key, http_proxy=http_proxy, connect=False) - self.assertEquals(sg.config.proxy_server, proxy_server) - self.assertEquals(sg.config.proxy_port, proxy_port) - self.assertEquals(sg.config.proxy_user, proxy_user) - self.assertEquals(sg.config.proxy_pass, proxy_pass) + self.assertEqual(sg.config.proxy_server, proxy_server) + self.assertEqual(sg.config.proxy_port, proxy_port) + self.assertEqual(sg.config.proxy_user, proxy_user) + self.assertEqual(sg.config.proxy_pass, proxy_pass) def test_http_proxy_with_at_in_password(self): proxy_server = "someserver.com" @@ -113,10 +113,10 @@ def test_http_proxy_with_at_in_password(self): self.api_key, http_proxy=http_proxy, connect=False) - self.assertEquals(sg.config.proxy_server, proxy_server) - self.assertEquals(sg.config.proxy_port, proxy_port) - self.assertEquals(sg.config.proxy_user, proxy_user) - self.assertEquals(sg.config.proxy_pass, proxy_pass) + self.assertEqual(sg.config.proxy_server, proxy_server) + self.assertEqual(sg.config.proxy_port, proxy_port) + self.assertEqual(sg.config.proxy_user, proxy_user) + self.assertEqual(sg.config.proxy_pass, proxy_pass) def test_malformatted_proxy_info(self): conn_info = { @@ -172,7 +172,7 @@ def test_filters(self): args = ['', [[path, relation, value]], None] result = self.get_call_rpc_params(args, {}) actual_condition = result['filters']['conditions'][0] - self.assertEquals(expected_condition, actual_condition) + self.assertEqual(expected_condition, actual_condition) @patch('shotgun_api3.Shotgun._call_rpc') def get_call_rpc_params(self, args, kws, call_rpc): @@ -262,8 +262,8 @@ def assert_platform(self, sys_ret_val, expected): expected_local_path_field = "local_path_%s" % expected client_caps = api.shotgun.ClientCapabilities() - self.assertEquals(client_caps.platform, expected) - self.assertEquals(client_caps.local_path_field, expected_local_path_field) + self.assertEqual(client_caps.platform, expected) + self.assertEqual(client_caps.local_path_field, expected_local_path_field) finally: api.shotgun.sys.platform = platform @@ -272,8 +272,8 @@ def test_no_platform(self): try: api.shotgun.sys.platform = "unsupported" client_caps = api.shotgun.ClientCapabilities() - self.assertEquals(client_caps.platform, None) - self.assertEquals(client_caps.local_path_field, None) + self.assertEqual(client_caps.platform, None) + self.assertEqual(client_caps.local_path_field, None) finally: api.shotgun.sys.platform = platform @@ -285,7 +285,7 @@ def test_py_version(self, mock_sys): mock_sys.version_info = (major, minor, micro, 'final', 0) expected_py_version = "%s.%s" % (major, minor) client_caps = api.shotgun.ClientCapabilities() - self.assertEquals(client_caps.py_version, expected_py_version) + self.assertEqual(client_caps.py_version, expected_py_version) class TestFilters(unittest.TestCase): @@ -296,7 +296,7 @@ def test_empty(self): } result = api.shotgun._translate_filters([], None) - self.assertEquals(result, expected) + self.assertEqual(result, expected) def test_simple(self): filters = [ @@ -313,7 +313,7 @@ def test_simple(self): } result = api.shotgun._translate_filters(filters, "any") - self.assertEquals(result, expected) + self.assertEqual(result, expected) # Test both styles of passing arrays def test_arrays(self): @@ -329,14 +329,14 @@ def test_arrays(self): ] result = api.shotgun._translate_filters(filters, "all") - self.assertEquals(result, expected) + self.assertEqual(result, expected) filters = [ ["code", "in", ["test1", "test2", "test3"]] ] result = api.shotgun._translate_filters(filters, "all") - self.assertEquals(result, expected) + self.assertEqual(result, expected) def test_nested(self): filters = [ @@ -379,7 +379,7 @@ def test_nested(self): } result = api.shotgun._translate_filters(filters, "all") - self.assertEquals(result, expected) + self.assertEqual(result, expected) def test_invalid(self): self.assertRaises(api.ShotgunError, api.shotgun._translate_filters, [], "bogus") @@ -461,7 +461,7 @@ def test_found_correct_cert(self): os.path.join(os.path.dirname(api.__file__), "lib", "certifi", "cacert.pem") ) # Now ensure that the path the SG API has found is correct. - self.assertEquals(cert_path, self.certs) + self.assertEqual(cert_path, self.certs) self.assertTrue(os.path.isfile(self.certs)) def test_httplib(self): @@ -475,7 +475,7 @@ def test_httplib(self): # Now check that the good urls connect properly using the certs for url in self.test_urls: response, message = self._check_url_with_sg_api_httplib2(url, self.certs) - self.assertEquals(response["status"], "200") + self.assertEqual(response["status"], "200") def test_urlib(self): """ From 6a48fb9ed6b3d5a53f17f4b1a0577320dd0b0a33 Mon Sep 17 00:00:00 2001 From: Norberto Moreno Date: Fri, 1 Apr 2022 09:21:36 -0500 Subject: [PATCH 006/125] Fix git protocol for installing python-api (#262) * Fix git protocol for installing python-api --- docs/installation.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 2a01630d6..c4f812c2a 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -37,7 +37,7 @@ Installing the Master Branch From Github ======================================== If you wish to install the current master, use the following command:: - pip install git+git://github.com/shotgunsoftware/python-api.git + pip install git+https://github.com/shotgunsoftware/python-api.git .. note:: The master branch contains the latest revisions and while largely considered "stable" it is not an official packaged release. @@ -47,14 +47,14 @@ Installing A specific Version From Github To install a specific version of the package from Github, run the following command. This example installs the v3.0.26 tag, replace the version tag with the one you want:: - pip install git+git://github.com/shotgunsoftware/python-api.git@v3.0.26 + pip install git+https://github.com/shotgunsoftware/python-api.git@v3.0.26 ``requirements.txt`` ~~~~~~~~~~~~~~~~~~~~ If you're using pip with `requirements.txt`, add the following line:: - git+git://github.com/shotgunsoftware/python-api.git + git+https://github.com/shotgunsoftware/python-api.git **************************** From e640baed8d2a9a982ce545eb7af4e9f8f9632ce4 Mon Sep 17 00:00:00 2001 From: Norberto Moreno Date: Tue, 31 May 2022 10:31:33 -0500 Subject: [PATCH 007/125] SG-20154 Add Retries on 503 Errors when uploading to S3 in Shotgun python-api (#263) * Add the 503 retries in the internal function who maker the S3 uploads. * Mock the S3 response with a expected HTTPError exception, for test 503 responses are retried when uploading to S3. * Adding side_effect parameter to the mock object. * Add comment in the _setup_mock() _make_upload_request definition. * Rename test and add expected HTTPError exception error message to the assertion. --- shotgun_api3/shotgun.py | 55 ++++++++++++++++++++++++++++++++--------- tests/base.py | 13 +++++++++- tests/test_client.py | 25 +++++++++++++++++-- 3 files changed, 78 insertions(+), 15 deletions(-) diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index 7e48573d2..522816104 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -3588,6 +3588,18 @@ def _http_request(self, verb, path, body, headers): return (http_status, resp_headers, resp_body) + def _make_upload_request(self, request, opener): + """ + Open the given request object, return the + response, raises URLError on protocol errors. + """ + try: + result = opener.open(request) + + except urllib.error.HTTPError: + raise + return result + def _parse_http_status(self, status): """ Parse the status returned from the http request. @@ -4049,21 +4061,40 @@ def _upload_data_to_storage(self, data, content_type, size, storage_url): :returns: upload url. :rtype: str """ - try: - opener = self._build_opener(urllib.request.HTTPHandler) + opener = self._build_opener(urllib.request.HTTPHandler) + + request = urllib.request.Request(storage_url, data=data) + request.add_header("Content-Type", content_type) + request.add_header("Content-Length", size) + request.get_method = lambda: "PUT" + + attempt = 1 + max_attempts = 4 # Three retries on failure + backoff = 0.75 # Seconds to wait before retry, times the attempt number + + while attempt <= max_attempts: + try: + result = self._make_upload_request(request, opener) + + LOG.debug("Completed request to %s" % request.get_method()) + + except urllib.error.HTTPError as e: + if e.code == 500: + raise ShotgunError("Server encountered an internal error.\n%s\n%s\n\n" % (storage_url, e)) + elif attempt != max_attempts and e.code == 503: + LOG.debug("Got a 503 response. Waiting and retrying...") + time.sleep(float(attempt) * backoff) + attempt += 1 + continue + else: + if e.code == 503: + raise ShotgunError("Got a 503 response when uploading to %s: %s" % (storage_url, e)) + raise ShotgunError("Unanticipated error occurred uploading to %s: %s" % (storage_url, e)) - request = urllib.request.Request(storage_url, data=data) - request.add_header("Content-Type", content_type) - request.add_header("Content-Length", size) - request.get_method = lambda: "PUT" - result = opener.open(request) - etag = result.info()["Etag"] - except urllib.error.HTTPError as e: - if e.code == 500: - raise ShotgunError("Server encountered an internal error.\n%s\n%s\n\n" % (storage_url, e)) else: - raise ShotgunError("Unanticipated error occurred uploading to %s: %s" % (storage_url, e)) + break + etag = result.info()["Etag"] LOG.debug("Part upload completed successfully.") return etag diff --git a/tests/base.py b/tests/base.py index 8d0e3a5bb..c6a2d5926 100644 --- a/tests/base.py +++ b/tests/base.py @@ -9,6 +9,7 @@ from shotgun_api3.shotgun import json from shotgun_api3.shotgun import ServerCapabilities from shotgun_api3.lib import six +from shotgun_api3.lib.six.moves import urllib if six.PY2: from shotgun_api3.lib.six.moves.configparser import SafeConfigParser as ConfigParser @@ -128,7 +129,17 @@ def _setup_mock(self): # eaiser than mocking the http connection + response self.sg._http_request = mock.Mock(spec=api.Shotgun._http_request, return_value=((200, "OK"), {}, None)) - + # Replace the function used to make the final call to the S3 server, and simulate + # the exception HTTPError raised with 503 status errors + self.sg._make_upload_request = mock.Mock(spec=api.Shotgun._make_upload_request, + side_effect = urllib.error.HTTPError( + "url", + 503, + "The server is currently down or to busy to reply." + "Please try again later.", + {}, + None + )) # also replace the function that is called to get the http connection # to avoid calling the server. OK to return a mock as we will not use # it diff --git a/tests/test_client.py b/tests/test_client.py index db4b93bdc..540b74710 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -37,7 +37,6 @@ from shotgun_api3.shotgun import ServerCapabilities, SG_TIMEZONE from . import base - if six.PY3: from base64 import encodebytes as base64encode else: @@ -196,7 +195,6 @@ def test_split_url(self): sg = api.Shotgun("https://ci.shotgunstudio.com", "foo", "bar", connect=False) - base_url = "https://ci.shotgunstudio.com" expected_server = "ci.shotgunstudio.com" expected_auth = None @@ -439,6 +437,29 @@ def test_call_rpc(self): self._mock_http(d, status=(502, "bad gateway")) self.assertRaises(api.ProtocolError, self.sg._call_rpc, "list", a) + def test_upload_s3(self): + """ + Test 503 response is retried when uploading to S3. + """ + this_dir, _ = os.path.split(__file__) + storage_url = "http://foo.com/" + path = os.path.abspath(os.path.expanduser( + os.path.join(this_dir, "sg_logo.jpg"))) + max_attempts = 4 # Max retries to S3 server attempts + # Expected HTTPError exception error message + expected = "The server is currently down or to busy to reply." \ + "Please try again later." + + # Test the Internal function that is used to upload each + # data part in the context of multi-part uploads to S3, we + # simulate the HTTPError exception raised with 503 status errors + with self.assertRaises(api.ShotgunError, msg=expected): + self.sg._upload_file_to_storage(path, storage_url) + # Test the max retries attempt + self.assertTrue( + max_attempts == self.sg._make_upload_request.call_count, + "Call is repeated up to 3 times") + def test_transform_data(self): """Outbound data is transformed""" timestamp = time.time() From 2f11512268105e4589475f45176b6ebd9e6b597a Mon Sep 17 00:00:00 2001 From: Ariel Calzada Date: Thu, 9 Jun 2022 16:01:58 -0500 Subject: [PATCH 008/125] Packaging for 3.3.4 (#266) * Add entry in HISTORY.rst, setup.py and shotgun.py for the version 3.3.4 * Complement description of python 3.9 coverage * Add s in verbs in the history --- HISTORY.rst | 7 +++++++ setup.py | 2 +- shotgun_api3/shotgun.py | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index cf252a7b1..7f65b0c4f 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -4,6 +4,13 @@ ShotGrid Python API Changelog Here you can see the full list of changes between each Python API release. +v3.3.4 (2022 June 9) +==================== +- Adds Retries on 503 Errors when uploading to S3. +- Updates AMI Documentation to Support Python 3. +- Adds Python 3.9 coverage in Azure Pipeline CI tests. +- Fixes git protocol for the installation. + v3.3.3 (2021 December 1) ========================== - Replaces shotgunsoftware urls with Autodesk Knowledge Network and ShotGrid Developer Documentation pages. diff --git a/setup.py b/setup.py index e1e0f958f..923da82e4 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ setup( name='shotgun_api3', - version='3.3.3', + version='3.3.4', description='Shotgun Python API ', long_description=readme, author='Shotgun Software', diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index 522816104..928a1e3c1 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -122,7 +122,7 @@ def _is_mimetypes_broken(): # ---------------------------------------------------------------------------- # Version -__version__ = "3.3.3" +__version__ = "3.3.4" # ---------------------------------------------------------------------------- # Errors From 9858837b7983b4d4c71ac271a5f9f8853da45254 Mon Sep 17 00:00:00 2001 From: francoperroneautodesk <104847741+francoperroneautodesk@users.noreply.github.com> Date: Wed, 13 Jul 2022 11:31:53 -0300 Subject: [PATCH 009/125] SG-26524_SG Api Overview (#267) Replace docs/images/scripts_page.png screenshot. --- docs/images/scripts_page.png | Bin 87417 -> 73074 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/images/scripts_page.png b/docs/images/scripts_page.png index f4d915a589251995017bbded43a53445be982990..fdbc3c38d664f8c8aa783f57a9420812fcd64006 100644 GIT binary patch literal 73074 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
    uRH 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∈%qd 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 Date: Thu, 24 Nov 2022 04:14:25 -0800 Subject: [PATCH 010/125] SG-29004 Upgrade deprecated azure pipelines machines (#270) * Change macos image version * Restore master ci pipeline execution to master branch * Update windows, ubuntu and mac images to latest versions supported by azure pipelines * Try linux with ubuntu 20.04 --- azure-pipelines.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index d787db252..cc8f6b887 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -63,9 +63,9 @@ jobs: - template: azure-pipelines-templates/run-tests.yml parameters: name: macOS - vm_image: 'macOS-10.15' + vm_image: 'macOS-12' - template: azure-pipelines-templates/run-tests.yml parameters: name: Windows - vm_image: 'windows-2019' + vm_image: 'windows-2022' From d33cda134848984ca8e5c4a1c1d5fa445f77c4e0 Mon Sep 17 00:00:00 2001 From: Shayna Duguid Date: Mon, 12 Dec 2022 16:53:38 -0500 Subject: [PATCH 011/125] [duguids] SG-29350 adding video to installation doc (#271) * SG-29350 adding video to installation doc * attempting embed with html and hyperlinking * update with Norberto's research * another attempt at the hyperlink * following tutorial hyperlink instructions Co-authored-by: Shayna Duguid --- docs/index.rst | 9 +++++++++ docs/installation.rst | 4 +++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index 85e4f2ef2..93ad51cf1 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -18,6 +18,15 @@ Because the needs of every studio can prove to be very different, we don't inclu decisions to you. The API is powerful enough you can write your own "smarts" in a wrapper on top of the Shotgun API. +.. _pythonoverviewvideo: + +Overview Video of Setting Up Your Environment with the Python API +================================================================= + +.. raw:: html + + + In addition to basic metadata, the API contains methods for managing media including thumbnails, filmstrip thumbnails, images, uploaded, and both locally and remotely linked media like Quicktimes, etc. diff --git a/docs/installation.rst b/docs/installation.rst index c4f812c2a..b2065bfd4 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -18,7 +18,6 @@ Minimum Requirements your version of Shotgun will raise an appropriate exception. In general, we attempt to document these where possible. - ****************************** Installing into ``PYTHONPATH`` ****************************** @@ -29,6 +28,9 @@ You'll need to save it somewhere your local Python installation can find it. .. seealso:: For more information on ``PYTHONPATH`` and using modules in Python, see http://docs.python.org/tutorial/modules.html +.. note:: + :ref:`Visit the introduction to the Python API ` to see an overview video of Setting Up Your Environment with the Python API. + *********************** Installing with ``pip`` *********************** From 6c0ae36648f063af8e613f739ba0ecd2a5e28325 Mon Sep 17 00:00:00 2001 From: Shayna Duguid Date: Wed, 14 Dec 2022 13:14:07 -0500 Subject: [PATCH 012/125] [duguids] SG-29232- replacing shotgun with shotgrid (#274) * SG-29232 replacing shotgun w shotgrid * Updated with Julien's feedback Co-authored-by: Shayna Duguid --- README.md | 4 +- docs/advanced/packaging.rst | 6 +-- docs/authentication.rst | 32 ++++++------ docs/cookbook.rst | 8 +-- docs/cookbook/attachments.rst | 35 +++++++------ .../examples/ami_version_packager.rst | 14 +++--- docs/cookbook/examples/basic_create_shot.rst | 12 ++--- .../basic_create_shot_task_template.rst | 6 +-- .../basic_create_version_link_shot.rst | 4 +- docs/cookbook/examples/basic_delete_shot.rst | 6 +-- docs/cookbook/examples/basic_find_shot.rst | 8 +-- docs/cookbook/examples/basic_sg_instance.rst | 12 ++--- docs/cookbook/examples/basic_update_shot.rst | 12 ++--- .../basic_upload_thumbnail_version.rst | 8 +-- docs/cookbook/examples/svn_integration.rst | 36 ++++++------- docs/cookbook/smart_cut_fields.rst | 2 +- docs/cookbook/tasks/split_tasks.rst | 2 +- docs/cookbook/tasks/task_dependencies.rst | 8 +-- docs/cookbook/tasks/updating_tasks.rst | 10 ++-- docs/cookbook/tutorials.rst | 2 +- docs/cookbook/usage_tips.rst | 16 +++--- docs/index.rst | 12 ++--- docs/installation.rst | 10 ++-- docs/reference.rst | 50 +++++++++---------- shotgun_api3/lib/README.md | 4 +- software_credits | 2 +- tests/example_config | 4 +- tests/mockgun/schema.pickle | 2 +- 28 files changed, 163 insertions(+), 164 deletions(-) diff --git a/README.md b/README.md index 8baae0cfb..12b9f6f1b 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![Build Status](https://dev.azure.com/shotgun-ecosystem/Python%20API/_apis/build/status/shotgunsoftware.python-api?branchName=master)](https://dev.azure.com/shotgun-ecosystem/Python%20API/_build/latest?definitionId=108&branchName=master) [![Coverage Status](https://coveralls.io/repos/github/shotgunsoftware/python-api/badge.svg?branch=master)](https://coveralls.io/github/shotgunsoftware/python-api?branch=master) -# Shotgun Python API +# ShotGrid Python API ShotGrid provides a simple Python-based API for accessing ShotGrid and integrating with other tools. This is the official API that is maintained by ShotGrid Software (https://knowledge.autodesk.com/contact-support) @@ -133,7 +133,7 @@ Integration and unit tests are provided. - Add more detailed information regarding the changes in this release. This is a great place to add examples, and reasons for the change! ### Letting the world know -Post a message in the [Pipeline Community channel](https://community.shotgunsoftware.com/c/pipeline) and send an email to [shotgun-dev](https://groups.google.com/a/shotgunsoftware.com/forum/#!forum/shotgun-dev) with a link to the community post. +Post a message in the [Pipeline Community channel](https://community.shotgridsoftware.com/c/pipeline). ### Prepare for the Next Dev Cycle 1) Update the `__version__` value in `shotgun_api3/shotgun.py` to the next version number with `.dev` appended to it. For example, `v3.0.24.dev` diff --git a/docs/advanced/packaging.rst b/docs/advanced/packaging.rst index a09e159ad..630a32f44 100644 --- a/docs/advanced/packaging.rst +++ b/docs/advanced/packaging.rst @@ -14,7 +14,7 @@ There are caveats you need to be aware of when creating such an app. ******************************** HTTPS Validation and cacerts.txt ******************************** -When creating the connection to Shotgun a file is used to validate the Shotgun certificate. This +When creating the connection to ShotGrid a file is used to validate the ShotGrid certificate. This file is located at ``shotgun_api3/lib/httplib2/cacerts.txt``. Because this file is not a Python file imported by your application, py2app will not know to include it in your package, it will need to be explicitly specified in your ``setup.py`` file (edit the path based on the location @@ -31,10 +31,10 @@ following structure:: ./Contents/Resources/my_script.py Where in ``my_script.py`` you can access the ``cacerts.txt`` file using a relative path to pass it -into the Shotgun connection's constructor:: +into the ShotGrid connection's constructor:: ca_certs = os.path.join(os.path.dirname(__file__), 'shotgun_api3', 'cacerts.txt') - sg = shotgun_api3.Shotgun('https://yoursite.shotgunstudio.com', 'script_name', 'script_key', + sg = shotgun_api3.Shotgun('https://my-site.shotgrid.autodesk.com', 'script_name', 'script_key', ca_certs=ca_certs) The process for py2exe should be similar. \ No newline at end of file diff --git a/docs/authentication.rst b/docs/authentication.rst index 1e77ee279..445820248 100644 --- a/docs/authentication.rst +++ b/docs/authentication.rst @@ -2,14 +2,14 @@ Authentication ############## -In order to communicate with your Shotgun server via the API, you must provide valid authentication credentials. The API allows you to authenticate with user-based, or script-based credentials. +In order to communicate with your ShotGrid server via the API, you must provide valid authentication credentials. The API allows you to authenticate with user-based, or script-based credentials. ************************* User-based Authentication ************************* -When authenticating as a user, you provide your normal login and password when instantiating your :class:`shotgun_api3.Shotgun` object. The actions performed by this instance will be limited to your permission level just as they are in the Shotgun web application. :: +When authenticating as a user, you provide your normal login and password when instantiating your :class:`shotgun_api3.Shotgun` object. The actions performed by this instance will be limited to your permission level just as they are in the web application. :: - sg = shotgun_api3.Shotgun("https://piedpiper.shotgunstudio.com", + sg = shotgun_api3.Shotgun("https://my-site.shotgrid.autodesk.com", login="rhendriks", password="c0mPre$Hi0n") @@ -17,24 +17,24 @@ When authenticating as a user, you provide your normal login and password when i *************************** Script-based Authentication *************************** -In order to authenticate as a script, your script must be :ref:`registered with Shotgun and have a valid API key `. When creating your :class:`shotgun_api3.Shotgun` object, provide the ``script_name`` and ``api_key``.:: +In order to authenticate as a script, your script must be :ref:`registered with ShotGrid and have a valid API key `. When creating your :class:`shotgun_api3.Shotgun` object, provide the ``script_name`` and ``api_key``.:: - sg = shotgun_api3.Shotgun("https://piedpiper.shotgunstudio.com", + sg = shotgun_api3.Shotgun("https://my-site.shotgrid.autodesk.com", script_name="compress", api_key="0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef") -.. note:: When using script-based authentication, we **strongly** recommend you register each script separately with Shotgun and have individual API keys for each. This allows you to track down each of your scripts and the actions they are performing much more accurately in the event logs. +.. note:: When using script-based authentication, we **strongly** recommend you register each script separately with ShotGrid and have individual API keys for each. This allows you to track down each of your scripts and the actions they are performing much more accurately in the event logs. -.. _setting_up_shotgun: +.. _setting_up_shotgrid: Adding Script Users =================== -If you'll be using script-based authentication, you need to create a Script entity in Shotgun. To create a new key, click the + button on the "Scripts" page in the Admin section and give your script a useful name. It's a good idea to add any other relevant information that be be helpful to your other friendly Shotgun users such as a description of what the script does that is using this key, the email address of the maintainer, etc.: +If you'll be using script-based authentication, you need to create a Script entity in ShotGrid. To create a new key, click the + button on the "Scripts" page in the Admin section and give your script a useful name. It's a good idea to add any other relevant information that be be helpful to your other friendly ShotGrid users such as a description of what the script does that is using this key, the email address of the maintainer, etc.: .. image:: images/scripts_page.png -Once you save your new Script entity, Shotgun will automatically generate an application key which will act as the script's password. The key will look something like this: ``0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef``. +Once you save your new Script entity, ShotGrid will automatically generate an application key which will act as the script's password. The key will look something like this: ``0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef``. Why have different application keys for different scripts? ========================================================== @@ -42,24 +42,24 @@ We recommend you create a new Script entity (and application key) for each scrip Event Logging ============= -By default, events generated by scripts using an script-based authentication are logged in Shotgun's event log. You can turn this off by un-checking the "Generate Events" checkbox either in the script detail page or from the main Scripts admin page in Shotgun. +By default, events generated by scripts using an script-based authentication are logged in ShotGrid's event log. You can turn this off by un-checking the "Generate Events" checkbox either in the script detail page or from the main Scripts admin page in ShotGrid. .. note:: Turning off event logging will also prevent any email notifications from being triggered by your scripts since the email notifier relies on the event log to find events to notify for. -Scripts using user-based authentication will generate events similarly to if you were performing the same actions in the Shotgun web application, though there is some additional metadata stored in the ``EventLogEntry`` that identifies the event as created from a script acting on behalf of the user. +Scripts using user-based authentication will generate events similarly to if you were performing the same actions in the ShotGrid web application, though there is some additional metadata stored in the ``EventLogEntry`` that identifies the event as created from a script acting on behalf of the user. Why would you want to turn event logging off for scripts? --------------------------------------------------------- -It is an optimization that is not used often, but some users have integration scripts that are pushing data into Shotgun just for reference, like publishes from their asset management system. This publish data is never changed later, so the data itself has the entire history, and the events would just clutter the event log. The event log can grow very large. So if you have no need to audit the history of what your script does, and it's generating an large amount of event log entries, you may find it's not necessary to create these events. +It is an optimization that is not used often, but some users have integration scripts that are pushing data into ShotGrid just for reference, like publishes from their asset management system. This publish data is never changed later, so the data itself has the entire history, and the events would just clutter the event log. The event log can grow very large. So if you have no need to audit the history of what your script does, and it's generating an large amount of event log entries, you may find it's not necessary to create these events. *********** Permissions *********** -Users and scripts are both bound by the restrictions of their permission role in Shotgun. The permission role is assigned by the **Permission Role** field for each entity type. +Users and scripts are both bound by the restrictions of their permission role in ShotGrid. The permission role is assigned by the **Permission Role** field for each entity type. -For Scripts, the default permission role is "API Admin User" which allows full access to create, update, and delete entities and fields, including editing the "date created" audit field and creating event log entries. If you have other permission roles for ApiUsers, you can set the default role that will be assigned when a new script is created, in your Shotgun site preferences. +For Scripts, the default permission role is "API Admin User" which allows full access to create, update, and delete entities and fields, including editing the "date created" audit field and creating event log entries. If you have other permission roles for ApiUsers, you can set the default role that will be assigned when a new script is created, in your ShotGrid site preferences. -When using user-based authentication in your script, it will be bound by the permission role assigned to you in Shotgun. For example, if you don't have access to edit the status field on Shots, your script won't be able to either. Attempting to perform actions that are prohibited by permissions will raise an appropriate exception. +When using user-based authentication in your script, it will be bound by the permission role assigned to you in ShotGrid. For example, if you don't have access to edit the status field on Shots, your script won't be able to either. Attempting to perform actions that are prohibited by permissions will raise an appropriate exception. -.. seealso:: `Shotgun Support: Permissions `_ +.. seealso:: `Permissions Documentation `_ diff --git a/docs/cookbook.rst b/docs/cookbook.rst index 54f4d983f..756c44c5b 100644 --- a/docs/cookbook.rst +++ b/docs/cookbook.rst @@ -29,7 +29,7 @@ and paste any of these into your own scripts. .. rubric:: Working With Files You'll probably be doing some work with files at your studio. This is a deep dive into some of -the inners of how Shotgun handles files (also called Attachments) and the different ways to link +the inners of how ShotGrid handles files (also called Attachments) and the different ways to link to them. .. toctree:: @@ -39,9 +39,9 @@ to them. .. rubric:: Working With Tasks -Scheduling is a complex beast. Shotgun can handle lots of different types of functionality around +Scheduling is a complex beast. ShotGrid can handle lots of different types of functionality around scheduling like split tasks, dependencies, and more. These docs walk you through the details of -how Shotgun thinks when it's handling Task changes and how you can make your scripts do what you +how ShotGrid thinks when it's handling Task changes and how you can make your scripts do what you need to do. .. toctree:: @@ -52,7 +52,7 @@ need to do. .. rubric:: Smart Cut Fields Smart Cut Fields are deprecated in favor of the -`new cut support added in Shotgun v7.0 `_. +`new cut support added in ShotGrid v7.0 `_. This documentation remains only to support studios who may not have upgraded to the new cut support features. diff --git a/docs/cookbook/attachments.rst b/docs/cookbook/attachments.rst index 59bffb408..5e41f0bd6 100644 --- a/docs/cookbook/attachments.rst +++ b/docs/cookbook/attachments.rst @@ -4,27 +4,26 @@ Details About Working With Files ################################ -The Shotgun web application stores Files as Attachment entities. You can see these on a Files page, +The ShotGrid web application stores Files as Attachment entities. You can see these on a Files page, or a Files tab on a detail page, for example. You can access Attachments via the API to create and modify uploaded files, url links, and local files, and link them to other entities (Shots, -Versions, etc). This entity works a lot like other entity types within Shotgun with a few +Versions, etc). This entity works a lot like other entity types within ShotGrid with a few exceptions which are detailed below. .. note:: - If you are simply looking for information about how to upload and link things in Shotgun, this + If you are simply looking for information about how to upload and link things in ShotGrid, this doc is not for you. Instead look at the :meth:`~shotgun_api3.Shotgun.upload` and :meth:`~shotgun_api3.Shotgun.upload_thumbnail` methods. This doc describes the detailed structure of the Attachment entities that represent files - in Shotgun and how to interact with them. If that sounds cool too, then read on! + in ShotGrid and how to interact with them. If that sounds cool too, then read on! .. versionadded:: 3.0.3 - Requires Shotgun Server v2.2.0+ ***************** Default structure ***************** -The following is a list of the default fields that Shotgun creates for Attachments. Your server +The following is a list of the default fields that ShotGrid creates for Attachments. Your server instance may look slightly different depending on your own customizations. Many of these fields are optional and some are automatically filled in. These exceptions are listed below in the descriptions of each field. @@ -47,7 +46,7 @@ descriptions of each field. the size of the file in bytes. - **id** (:obj:`int`): - The internal Shotgun id for this Attachment entity. + The internal ShotGrid id for this Attachment entity. - **attachment_links** (:obj:`list`): A list of entity dictionaries used for linking Attachments to multiple entities. @@ -75,7 +74,7 @@ descriptions of each field. List of tags (as strings) that are currently assigned to the Attachment. - **image** (:obj:`str`): - The url location of the thumbnail image assigned to this Attachment. For uploads, Shotgun + The url location of the thumbnail image assigned to this Attachment. For uploads, ShotGrid automatically tries to create a thumbnail from the file. See :ref:`interpreting_image_field_strings`. Alternatively, you can assign your own thumbnail to an Attachment using the :meth:`~shotgun_api3.Shotgun.upload_thumbnail` method. @@ -95,8 +94,8 @@ Depending on the type of file the Attachment entity is representing, the value o will vary. - **Uploads** - Designated by ``link_type: 'upload'``, this represents a file that was uploaded to Shotgun. - Uploading files to Shotgun can be done using the :meth:`~shotgun_api3.Shotgun.upload` method. + Designated by ``link_type: 'upload'``, this represents a file that was uploaded to ShotGrid. + Uploading files to ShotGrid can be done using the :meth:`~shotgun_api3.Shotgun.upload` method. You cannot create an Attachment with an uploaded file directly. :: @@ -104,7 +103,7 @@ will vary. {'content_type': 'image/jpeg', 'link_type': 'upload', 'name': 'western1FULL.jpg', - 'url': 'https://superdeathcarracer.shotgunstudio.com/file_serve/attachment/538'} + 'url': 'https://my-site.shotgrid.autodesk.com/file_serve/attachment/538'} - **Web links** Designated by ``link_type: 'web'``, this is represents a url link. Examples include an @@ -175,14 +174,14 @@ Updating Attachments ******************** You cannot modify the ``this_file`` field after you create an Attachment. If you need to provide a different file, you will have to create a new Attachment entity. Otherwise, the process for -updating Attachments is exactly like updating other entity types in Shotgun and is the same for all +updating Attachments is exactly like updating other entity types in ShotGrid and is the same for all Attachment types. See :meth:`~shotgun_api3.Shotgun.update` for more info. ******************** Deleting Attachments ******************** -The process of deleting an Attachment is just like other entities in Shotgun. See +The process of deleting an Attachment is just like other entities in ShotGrid. See :meth:`~shotgun_api3.Shotgun.delete` for more info. .. _local_files: @@ -191,14 +190,14 @@ The process of deleting an Attachment is just like other entities in Shotgun. Se Working With Local File Types ***************************** -We added support for linking to local files in the UI in Shotgun Server v2.1. This doc covers how +We added support for linking to local files in the UI in ShotGrid Server v2.1. This doc covers how to work with these local file links using the API. Requirements ============ - Python API v3.0.3+ -- Shotgun Server v2.1.10+ +- ShotGrid Server v2.1.10+ Structure of Local File Values ============================== @@ -276,7 +275,7 @@ Returns:: Creating & Updating Local file Fields ===================================== -When setting a file/link field value to a local file, only the ``local_path`` is mandatory. Shotgun +When setting a file/link field value to a local file, only the ``local_path`` is mandatory. ShotGrid will automatically select the appropriate matching local storage for your file based on the path. You can optionally specify the ``name`` and ``content_type`` fields if you wish to override their defaults. Any other keys that are provided will be ignored. @@ -290,7 +289,7 @@ defaults. Any other keys that are provided will be ignored. Optional display name of the local file. This is set to the filename by default. * **local_path** :obj:`str`: - The full local path to the file. Shotgun will find the LocalStorage + The full local path to the file. ShotGrid will find the LocalStorage that has the most specific match to this path and automatically assign that LocalStorage to the file. @@ -316,7 +315,7 @@ Returns:: 'url': 'file:///Users/kp/Movies/testing/test_movie_002.mov'}, 'type': 'Version'}] -The ``content_type`` was assigned a best-guess value based on the file extension. Shotgun selected +The ``content_type`` was assigned a best-guess value based on the file extension. ShotGrid selected the most appropriate specific LocalStorage match and assigned it to local_storage automatically. Un-setting local file field values diff --git a/docs/cookbook/examples/ami_version_packager.rst b/docs/cookbook/examples/ami_version_packager.rst index 445a38d93..3ac7917d0 100644 --- a/docs/cookbook/examples/ami_version_packager.rst +++ b/docs/cookbook/examples/ami_version_packager.rst @@ -20,7 +20,7 @@ It is intended to be used in conjunction with the script dicussed in :ref:`ami_h """ version_packager.py - This example script is meant to be run from an ActionMenuItem in Shotgun. The menu item uses a custom + This example script is meant to be run from an ActionMenuItem in ShotGrid. The menu item uses a custom protocol in order to launch this script, and is followed by the action 'package4client'. So the full url would be something like launchme://package4client?.... See: https://developer.shotgridsoftware.com/python-api/cookbook/examples/ami_handler.html @@ -29,9 +29,9 @@ It is intended to be used in conjunction with the script dicussed in :ref:`ami_h POST variables. For more information about it and accessing the variables in the ActionMenuItem POST request, See: http://developer.shotgridsoftware.com/python-api/examples/ami_handler - The purpose of this script is to download attachment files from Shotgun, create an archive of them + The purpose of this script is to download attachment files from ShotGrid, create an archive of them and copy them to a specified directory. You can invoke it with the following minimal example to connect - to Shotgun, download any file that exists in the specified field ('sg_qt') for each selected_id passed from the + to ShotGrid, download any file that exists in the specified field ('sg_qt') for each selected_id passed from the ActionMenu. Then it will create a single archive of the files and move it to the specified directory ('/path/where/i/want/to/put/the/archive/'). The archive is named with the Project Name, timestamp, and user login who ran the ActionMenuItem ('Demo_Project_2010-04-29-172210_kp.tar.gz'): @@ -59,9 +59,9 @@ It is intended to be used in conjunction with the script dicussed in :ref:`ami_h # --------------------------------------------------------------------------------------------- # Variables # --------------------------------------------------------------------------------------------- - # Shotgun server auth info + # ShotGrid server auth info shotgun_conf = { - 'url':'https://YOURSTUDIO.shotgunstudio.com', + 'url':'https://my-site.shotgrid.autodesk.com', 'name':'YOUR_SCRIPT_NAME_HERE', 'key':'YOUR_SCRIPT_KEY_HERE' } @@ -79,7 +79,7 @@ It is intended to be used in conjunction with the script dicussed in :ref:`ami_h # ---------------------------------------------- - # Generic Shotgun Exception Class + # Generic ShotGrid Exception Class # ---------------------------------------------- class ShotgunException(Exception): pass @@ -125,7 +125,7 @@ It is intended to be used in conjunction with the script dicussed in :ref:`ami_h attachment_id = extract_attachment_id(attachment) if type(attachment_id) != int: return None - # download the attachment file from Shotgun and write it to local disk + # download the attachment file from ShotGrid and write it to local disk logger.info("Downloading Attachment #%s" % (attachment_id)) stream = sg.download_attachment(attachment_id) try: diff --git a/docs/cookbook/examples/basic_create_shot.rst b/docs/cookbook/examples/basic_create_shot.rst index 9f74de4bc..5c70585cf 100644 --- a/docs/cookbook/examples/basic_create_shot.rst +++ b/docs/cookbook/examples/basic_create_shot.rst @@ -27,7 +27,7 @@ This will create a new Shot named "100_010" in the Project "Gunslinger" (which h - ``data`` is a list of key/value pairs where the key is the column name to update and the value is the the value to set. -- ``sg`` is the Shotgun API instance you created in :ref:`example_sg_instance`. +- ``sg`` is the ShotGrid API instance you created in :ref:`example_sg_instance`. - ``create()`` is the :meth:`shotgun_api3.Shotgun.create` API method we are calling. We pass in the entity type we're searching for and the data we're setting. @@ -44,11 +44,11 @@ The variable ``result`` now contains a dictionary hash with the Shot information 'type': 'Shot' } -In addition, Shotgun has returned the ``id`` that it has assigned to the Shot, as well as a +In addition, ShotGrid has returned the ``id`` that it has assigned to the Shot, as well as a ``type`` value. ``type`` is provided for convenience simply to help you identify what entity type -this dictionary represents. It does not correspond to any field in Shotgun. +this dictionary represents. It does not correspond to any field in ShotGrid. -Shotgun will *always* return the ``id`` and ``type`` keys in the dictionary when there are results +ShotGrid will *always* return the ``id`` and ``type`` keys in the dictionary when there are results representing an entity. The Complete Example @@ -66,8 +66,8 @@ The Complete Example # -------------------------------------- # Globals # -------------------------------------- - # make sure to change this to match your Shotgun server and auth credentials. - SERVER_PATH = "https://mystudio.shotgunstudio.com" + # make sure to change this to match your ShotGrid server and auth credentials. + SERVER_PATH = "https://my-site.shotgrid.autodesk.com" SCRIPT_NAME = 'my_script' SCRIPT_KEY = '27b65d7063f46b82e670fe807bd2b6f3fd1676c1' diff --git a/docs/cookbook/examples/basic_create_shot_task_template.rst b/docs/cookbook/examples/basic_create_shot_task_template.rst index 76eb169e8..4144bf460 100644 --- a/docs/cookbook/examples/basic_create_shot_task_template.rst +++ b/docs/cookbook/examples/basic_create_shot_task_template.rst @@ -1,6 +1,6 @@ Create a Shot with a Task Template ================================== -Creating a new Shot with a Task Template is just like linking it to another entity, but Shotgun will apply the Task Template in the background and create the appropriate Tasks on the Shot for you. +Creating a new Shot with a Task Template is just like linking it to another entity, but ShotGrid will apply the Task Template in the background and create the appropriate Tasks on the Shot for you. Find the Task Template ---------------------- @@ -31,7 +31,7 @@ Now we can create the Shot with the link to the ``TaskTemplate`` to apply. result = sg.create('Shot', data) This will create a new Shot named "100_010" linked to the TaskTemplate "3D Shot Template" and -Shotgun will then create the Tasks defined in the template and link them to the Shot you just +ShotGrid will then create the Tasks defined in the template and link them to the Shot you just created. - ``data`` is a list of key/value pairs where the key is the column name to update and the value is @@ -65,7 +65,7 @@ If we now search for the Tasks linked to the Shot, we'll find the Tasks that mat tasks = sg.find('Task', filters=[['entity', 'is', result]]) .. note:: You can use an entity dictionary that was returned from the API in a filter as we have - done above. Shotgun will only look at the ``id`` and ``type`` keys and will ignore the rest. + done above. ShotGrid will only look at the ``id`` and ``type`` keys and will ignore the rest. This is a handy way to pass around entity dictionaries without having to reformat them. Now the ``tasks`` variable contains the following:: diff --git a/docs/cookbook/examples/basic_create_version_link_shot.rst b/docs/cookbook/examples/basic_create_version_link_shot.rst index d93983ebb..bdec9ef46 100644 --- a/docs/cookbook/examples/basic_create_version_link_shot.rst +++ b/docs/cookbook/examples/basic_create_version_link_shot.rst @@ -1,6 +1,6 @@ Create a Version Linked to a Shot ================================= -You've just created a sweet new Version of your shot. Now you want to update Shotgun and create a +You've just created a sweet new Version of your shot. Now you want to update ShotGrid and create a new ``Version`` entity linked to the Shot. Find the Shot @@ -28,7 +28,7 @@ variable from the previous search) and the Task Name, which maps to the ``conten .. note:: Linking a Task to the Version is good practice. By doing so it is easy for users to see at what stage a particular Version was created, and opens up other possibilities for tracking - in Shotgun. We highly recommend doing this whenever possible. + in ShotGrid. We highly recommend doing this whenever possible. Create the Version ------------------ diff --git a/docs/cookbook/examples/basic_delete_shot.rst b/docs/cookbook/examples/basic_delete_shot.rst index 779fc9659..5041ee269 100644 --- a/docs/cookbook/examples/basic_delete_shot.rst +++ b/docs/cookbook/examples/basic_delete_shot.rst @@ -3,7 +3,7 @@ Delete A Shot Calling :meth:`~shotgun_api3.Shotgun.delete` -------------------------------------------- -Deleting an entity in Shotgun is pretty straight-forward. No extraneous steps required.:: +Deleting an entity in ShotGrid is pretty straight-forward. No extraneous steps required.:: result = sg.delete("Shot", 40435) @@ -28,8 +28,8 @@ The Complete Example # -------------------------------------- # Globals # -------------------------------------- - # make sure to change this to match your Shotgun server and auth credentials. - SERVER_PATH = "https://mystudio.shotgunstudio.com" + # make sure to change this to match your ShotGrid server and auth credentials. + SERVER_PATH = "https://my-site.shotgrid.autodesk.com" SCRIPT_NAME = 'my_script' SCRIPT_KEY = '27b65d7063f46b82e670fe807bd2b6f3fd1676c1' diff --git a/docs/cookbook/examples/basic_find_shot.rst b/docs/cookbook/examples/basic_find_shot.rst index ee223c89b..19fed7cf9 100644 --- a/docs/cookbook/examples/basic_find_shot.rst +++ b/docs/cookbook/examples/basic_find_shot.rst @@ -14,7 +14,7 @@ Pretty simple right? Well here's a little more insight into what's going on. - ``filters`` is an list of filter conditions. In this example we are filtering for Shots where the ``id`` column is **40435**. -- ``sg`` is the Shotgun API instance. +- ``sg`` is the ShotGrid API instance. - ``find_one()`` is the :meth:`~shotgun_api3.Shotgun.find_one` API method we are calling. We provide it with the entity type we're searching for and our filters. @@ -26,7 +26,7 @@ So what does this return? The variable result now contains:: {'type': 'Shot','id': 40435} By default, :meth:`~shotgun_api3.Shotgun.find_one` returns a single dictionary object with -the ``type`` and ``id`` fields. So in this example, we found a Shot matching that id, and Shotgun +the ``type`` and ``id`` fields. So in this example, we found a Shot matching that id, and ShotGrid returned it as a dictionary object with ``type`` and ``id`` keys . How do we know that result contains the Shot dictionary object? You can trust us... but just to be @@ -52,8 +52,8 @@ The Complete Example # -------------------------------------- # Globals # -------------------------------------- - # make sure to change this to match your Shotgun server and auth credentials. - SERVER_PATH = "https://mystudio.shotgunstudio.com" + # make sure to change this to match your ShotGrid server and auth credentials. + SERVER_PATH = "https://my-site.shotgrid.autodesk.com" SCRIPT_NAME = 'my_script' SCRIPT_KEY = '27b65d7063f46b82e670fe807bd2b6f3fd1676c1' diff --git a/docs/cookbook/examples/basic_sg_instance.rst b/docs/cookbook/examples/basic_sg_instance.rst index 6d2d1dd08..fed8a6c51 100644 --- a/docs/cookbook/examples/basic_sg_instance.rst +++ b/docs/cookbook/examples/basic_sg_instance.rst @@ -1,18 +1,18 @@ .. _example_sg_instance: -Create a Shotgun API instance +Create a ShotGrid API instance ============================= -This example shows you how to establish your initial connection to Shotgun using script-based -authentication. ``sg`` represents your Shotgun API instance. Be sure you've read -:ref:`Setting Up Shotgun for API Access `. +This example shows you how to establish your initial connection to ShotGrid using script-based +authentication. ``sg`` represents your ShotGrid API instance. Be sure you've read +:ref:`Setting Up ShotGrid for API Access `. :: import pprint # Useful for debugging import shotgun_api3 - SERVER_PATH = "https://your_site.shotgunstudio.com" + SERVER_PATH = "https://my-site.shotgrid.autodesk.com" SCRIPT_NAME = 'my_script' SCRIPT_KEY = '27b65d7063f46b82e670fe807bd2b6f3fd1676c1' @@ -22,5 +22,5 @@ authentication. ``sg`` represents your Shotgun API instance. Be sure you've read # sg connection object pprint.pprint([symbol for symbol in sorted(dir(sg)) if not symbol.startswith('_')]) -For further information on what you can do with this Shotgun object you can read the +For further information on what you can do with this ShotGrid object you can read the :ref:`API reference `. \ No newline at end of file diff --git a/docs/cookbook/examples/basic_update_shot.rst b/docs/cookbook/examples/basic_update_shot.rst index 555114578..5d1b4ff9b 100644 --- a/docs/cookbook/examples/basic_update_shot.rst +++ b/docs/cookbook/examples/basic_update_shot.rst @@ -17,7 +17,7 @@ This will update the ``description`` and the ``sg_status_list`` fields for the S - ``data`` is a list of key/value pairs where the key is the field name to update and the value to update it to. -- ``sg`` is the Shotgun API instance. +- ``sg`` is the ShotGrid API instance. - ``update()`` is the :meth:`shotgun_api3.Shotgun.update` API method we are calling. We provide it with the entity type we're updating, the ``id`` of the entity, and the data we're updating it with. @@ -33,11 +33,11 @@ The variable ``result`` now contains the Shot object that with the updated value 'id': 40435 } -In addition, Shotgun has returned the ``id`` for the Shot, as well as a ``type`` value. ``type`` +In addition, ShotGrid has returned the ``id`` for the Shot, as well as a ``type`` value. ``type`` is provided for convenience simply to help you identify what entity type this dictionary represents. -It does not correspond to any field in Shotgun. +It does not correspond to any field in ShotGrid. -Shotgun will *always* return the ``id`` and ``type`` keys in the dictionary when there are results +ShotGrid will *always* return the ``id`` and ``type`` keys in the dictionary when there are results representing an entity. The Complete Example @@ -55,8 +55,8 @@ The Complete Example # -------------------------------------- # Globals # -------------------------------------- - # make sure to change this to match your Shotgun server and auth credentials. - SERVER_PATH = "https://mystudio.shotgunstudio.com" + # make sure to change this to match your ShotGrid server and auth credentials. + SERVER_PATH = "https://my-site.shotgrid.autodesk.com" SCRIPT_NAME = 'my_script' SCRIPT_KEY = '27b65d7063f46b82e670fe807bd2b6f3fd1676c1' diff --git a/docs/cookbook/examples/basic_upload_thumbnail_version.rst b/docs/cookbook/examples/basic_upload_thumbnail_version.rst index c2f221e11..ed8b51827 100644 --- a/docs/cookbook/examples/basic_upload_thumbnail_version.rst +++ b/docs/cookbook/examples/basic_upload_thumbnail_version.rst @@ -1,10 +1,10 @@ Upload a Thumbnail for a Version ================================ -So you've created a new Version of a Shot, and you've updated Shotgun, but now you want to upload a +So you've created a new Version of a Shot, and you've updated ShotGrid, but now you want to upload a beauty frame to display as the thumbnail for your Version. We'll assume you already have the image made (located on your machine at ``/v1/gun/s100/010/beauties/anim/100_010_animv1.jpg``) . And since -you've just created your Version in Shotgun, you know its ``id`` is **214**. +you've just created your Version in ShotGrid, you know its ``id`` is **214**. .. note:: If you upload a movie file or image to the ``sg_uploaded_movie`` field and you have transcoding enabled on your server (the default for hosted sites), a thumbnail will be @@ -18,9 +18,9 @@ Upload the Image using :meth:`~shotgun_api3.Shotgun.upload_thumbnail` sg.upload_thumbnail("Version", 214, "/v1/gun/s100/010/beauties/anim/100_010_animv1.jpg") -Shotgun will take care of resizing the thumbnail for you. If something does go wrong, an exception +ShotGrid will take care of resizing the thumbnail for you. If something does go wrong, an exception will be thrown and you'll see the error details. .. note:: The result returned by :meth:`~shotgun_api3.Shotgun.upload_thumbnail` is an integer - representing the id of a special ``Attachment`` entity in Shotgun. Working with Attachments + representing the id of a special ``Attachment`` entity in ShotGrid. Working with Attachments is beyond the scope of this example. :) \ No newline at end of file diff --git a/docs/cookbook/examples/svn_integration.rst b/docs/cookbook/examples/svn_integration.rst index b45c4bee4..c0c3730b5 100644 --- a/docs/cookbook/examples/svn_integration.rst +++ b/docs/cookbook/examples/svn_integration.rst @@ -4,10 +4,10 @@ Subversion (SVN) Integration ############################ -Integrating Shotgun with Subversion consists of two basic parts: +Integrating ShotGrid with Subversion consists of two basic parts: - Setup a post-commit hook in Subversion. -- Create a Shotgun API script to create the Revision in Shotgun. This script will be called by +- Create a ShotGrid API script to create the Revision in ShotGrid. This script will be called by the post-commit hook. **************** @@ -21,7 +21,7 @@ To setup the post-commit hook: as a starting point for the real thing. - Create your very own executable script, and save it in the same ``hooks`` folder, name it ``post-commit``, and give it executable permission. -- In your ``post-commit`` script, invoke your Shotgun API script. +- In your ``post-commit`` script, invoke your ShotGrid API script. If this is entirely new to you, we highly suggest reading up on the topic. O'Reilly has `a free online guide for Subversion 1.5 and 1.6 @@ -55,14 +55,14 @@ Explanation of selected lines our ``REPOS`` and ``REV`` values, first with the ``author``, and then with ``log`` subcommand. These are actually the first two original lines of code - everything else to this point was pre-written already in the ``post-commit.tmpl`` file. nice :) -- line ``10``: This is the absolute path to our Shotgun API Script. +- line ``10``: This is the absolute path to our ShotGrid API Script. ****************** -Shotgun API Script +ShotGrid API Script ****************** -This script will create the Revision and populate it with some metadata using the Shotgun Python -API. It will create our Revision in Shotgun along with the author, comment, and because we use +This script will create the Revision and populate it with some metadata using the ShotGrid Python +API. It will create our Revision in ShotGrid along with the author, comment, and because we use Trac (a web-based interface for Subversion), it will also populate a URL field with a clickable link to the Revision. @@ -83,7 +83,7 @@ link to the Revision. # --------------------------------------------------------------------------------------------- # Globals - update all of these values to those of your studio # --------------------------------------------------------------------------------------------- - SERVER_PATH = 'https ://studio_name.shotgunstudio.com' # or http: + SERVER_PATH = 'https ://my-site.shotgrid.autodesk.com' # or http: SCRIPT_USER = 'script_name' SCRIPT_KEY = '3333333333333333333333333333333333333333' REVISIONS_PATH = 'https ://serveraddress/trac/changeset/' # or other web-based UI @@ -105,7 +105,7 @@ link to the Revision. # Set the Trac path for this specific revision revision_url = REVISIONS_PATH + revision_code - # Validate that author is a valid Shotgun HumanUser + # Validate that author is a valid ShotGrid HumanUser result = sg.find_one("HumanUser", [['login', 'is', author]]) if result: # Create Revision @@ -121,33 +121,33 @@ link to the Revision. # Send error message if valid HumanUser is not found else: - print("Unable to find valid Shotgun User with login: "+author+", Revision not created in Shotgun.") + print("Unable to find valid ShotGrid User with login: "+author+", Revision not created in ShotGrid.") Explanation of selected lines: ============================== -- line ``14``: This should be the URL to your instance of Shotgun. +- line ``14``: This should be the URL to your instance of ShotGrid. - lines ``15-16``: Make sure you get these values from the "Scripts" page in the Admin section of - the Shotgun web application. If you're not sure how to do this, check out :doc:`authentication`. + the ShotGrid web application. If you're not sure how to do this, check out :doc:`authentication`. - line ``17``: This is the address of Trac, our web-based interface that we use with Subversion. You may use a different interface, or none at all, so feel free to adjust this line or ignore it as your case may be. -- line ``18``: Every Revision in Shotgun must have a Project, which is passed to the API as a +- line ``18``: Every Revision in ShotGrid must have a Project, which is passed to the API as a dictionary with two keys, the ``type`` and the ``id``. Of course the ``type`` value will always remain ``Project`` (case sensitive), but the ``id`` will change by Project. To find out the - ``id`` of your Project, go to the Projects page in the Shotgun web application, locate the + ``id`` of your Project, go to the Projects page in the ShotGrid web application, locate the Project where you want your Revisions created, and then locate its ``id`` field (which you may need to display - if you don't see it, right click on any column header then select "Insert Column" > "Id"). Note that for this example we assume that all Revisions in this Subversion repository will belong to the same Project. - lines ``28-31``: Grab the values from the objects that were left for us in the environment. - line ``34``: Add the Revision number to complete the path of our Trac url. -- line ``37``: Make sure that a valid User exists in Shotgun. In our example, we assume that our - Users' Shotgun logins match their Subversion names. If the user exists in Shotgun, that +- line ``37``: Make sure that a valid User exists in ShotGrid. In our example, we assume that our + Users' ShotGrid logins match their Subversion names. If the user exists in ShotGrid, that user's ``id`` will be returned as ``result['id']``, which we will need later on in line 46. -- lines ``40-48``: Use all the meta data we've gathered to create a Revision in Shotgun. If none +- lines ``40-48``: Use all the meta data we've gathered to create a Revision in ShotGrid. If none of these lines make any sense, check out more on the :meth:`~shotgun_api3.Shotgun.create` method here. Line 41 deserves special mention: notice that we define a dictionary called ``url`` that has three important keys: ``content_type``, ``url``, and ``name``, and we then pass this in as @@ -164,7 +164,7 @@ My post-commit script is simply not running. I can run it manually, but commits Make sure that the script is has explicitly been made executable and that all users who will invoke it have appropriate permissions for the script and that folders going back to root. -My Shotgun API script is not getting called by the post-commit hook. +My ShotGrid API script is not getting called by the post-commit hook. ==================================================================== Make sure that the script is called using its absolute path. diff --git a/docs/cookbook/smart_cut_fields.rst b/docs/cookbook/smart_cut_fields.rst index 8e47f87d9..23d8669da 100644 --- a/docs/cookbook/smart_cut_fields.rst +++ b/docs/cookbook/smart_cut_fields.rst @@ -5,7 +5,7 @@ Smart Cut Fields ################ .. warning:: - Smart Cut Fields should be considered deprecated. Shotgun v7.0, introduced a new version of + Smart Cut Fields should be considered deprecated. ShotGrid v7.0, introduced a new version of cut support. `Read the Cut Support Documentation here `_. If you want to work with 'smart' cut fields through the API, your script should use a corresponding diff --git a/docs/cookbook/tasks/split_tasks.rst b/docs/cookbook/tasks/split_tasks.rst index c8628c0f9..ade069f99 100644 --- a/docs/cookbook/tasks/split_tasks.rst +++ b/docs/cookbook/tasks/split_tasks.rst @@ -5,7 +5,7 @@ Split Tasks ########### Split tasks can be created and edited via the API but must comply to some rules. Before going -further, a good understanding of :ref:`how Shotgun handles task dates is useful `. +further, a good understanding of :ref:`how ShotGrid handles task dates is useful `. ******** Overview diff --git a/docs/cookbook/tasks/task_dependencies.rst b/docs/cookbook/tasks/task_dependencies.rst index b16c32ac3..48a09e686 100644 --- a/docs/cookbook/tasks/task_dependencies.rst +++ b/docs/cookbook/tasks/task_dependencies.rst @@ -5,7 +5,7 @@ Task Dependencies ################# Task dependencies work the same way in the API as they do in the UI. You can filter and sort on -any of the fields. For information about Task Dependencies in Shotgun, check out the `main +any of the fields. For information about Task Dependencies in ShotGrid, check out the `main documentation page on our support site `_ @@ -232,7 +232,7 @@ Our Tasks now look like this:: ... Because the "Anm" Task ``start_date`` depends on the ``due_date`` of the "Layout" Task, this -change creates a dependency violation. The update succeeds, but Shotgun has also set the +change creates a dependency violation. The update succeeds, but ShotGrid has also set the ``dependency_violation`` field to ``True`` and has also updated the ``pinned`` field to ``True``. The ``pinned`` field simply means that if the upstream Task(s) are moved, the "Anm" Task will no @@ -315,7 +315,7 @@ Updating the ``pinned`` field on a Task with a Dependency Violation Let's go back a couple of steps to where our "Anm" Task had a Dependency Violation because we had moved the Start Date up before the "Layout" Task End Date. Remember that the ``pinned`` field -was also ``True``. If we simply update the ``pinned`` field to be ``False``, Shotgun will also +was also ``True``. If we simply update the ``pinned`` field to be ``False``, ShotGrid will also automatically update the Task dates to satisfy the upstream dependencies and reset the ``dependency_violation`` value to ``False``:: @@ -349,7 +349,7 @@ Our Tasks now look like this:: ... -Notice by updating ``pinned`` to ``False``, Shotgun also updated the ``start_date`` and +Notice by updating ``pinned`` to ``False``, ShotGrid also updated the ``start_date`` and ``due_date`` fields of our "Anm" Task so it will satisfy the upstream Task dependencies. And since that succeeded, the ``dependency_violation`` field has also been set to ``False`` diff --git a/docs/cookbook/tasks/updating_tasks.rst b/docs/cookbook/tasks/updating_tasks.rst index 445ecdfc6..3f8f9d5b7 100644 --- a/docs/cookbook/tasks/updating_tasks.rst +++ b/docs/cookbook/tasks/updating_tasks.rst @@ -1,11 +1,11 @@ .. _updating_tasks: ####################################### -Updating Task Dates: How Shotgun Thinks +Updating Task Dates: How ShotGrid Thinks ####################################### When updating Task dates in an API update() request, there is no specified order to the values that -are passed in. Shotgun also does automatic calculation of the``start_date``,``due_date``, and ``duration`` fields for convenience. In order to clarify how updates are handled by Shotgun we are +are passed in. ShotGrid also does automatic calculation of the``start_date``,``due_date``, and ``duration`` fields for convenience. In order to clarify how updates are handled by ShotGrid we are providing some general rules below and examples of what will happen when you make updates to your Tasks. @@ -16,12 +16,12 @@ General Rules - Updating the ``start_date`` automatically updates the ``due_date`` (``duration`` remains constant) - Updating the ``due_date`` automatically updates the ``duration`` (``start_date`` remains constant) - Updating the ``duration`` automatically updates the ``due_date`` (``start_date`` remains constant) -- When updating Task values, Shotgun sets schedule fields (``milestone``, ``duration``, +- When updating Task values, ShotGrid sets schedule fields (``milestone``, ``duration``, ``start_date``, ``due_date``) after all other fields, because the Project and Task Assignees affect schedule calculations. - If ``start_date`` and ``due_date`` are both set, ``duration`` is ignored (``duration`` can often be wrong since it's easy to calculate scheduling incorrectly). -- If both ``start_date`` and ``due_date`` are provided, Shotgun sets ``start_date`` before +- If both ``start_date`` and ``due_date`` are provided, ShotGrid sets ``start_date`` before ``due_date``. - Set ``milestone`` before other schedule fields (because ``start_date``, ``due_date``, and ``duration`` get lost if ``milestone`` is not set to ``False`` first) @@ -39,7 +39,7 @@ Examples The following examples show what the resulting Task object will look like after being run on the initial Task object listed under the header of each section. -The ``duration`` values in the following examples assume your Shotgun instance is set to +The ``duration`` values in the following examples assume your ShotGrid instance is set to 10-hour work days. If your server is configured with a different setting, the ``duration`` values will vary. diff --git a/docs/cookbook/tutorials.rst b/docs/cookbook/tutorials.rst index 2eb2e0d4d..18235db89 100644 --- a/docs/cookbook/tutorials.rst +++ b/docs/cookbook/tutorials.rst @@ -3,7 +3,7 @@ Examples ######## Here's a list of various simple tutorials to walk through that should provide you with a good base -understanding of how to use the Shotgun API and what you can do with it. +understanding of how to use the ShotGrid API and what you can do with it. ***** Basic diff --git a/docs/cookbook/usage_tips.rst b/docs/cookbook/usage_tips.rst index 8a909f7fc..0a0fb5d5e 100644 --- a/docs/cookbook/usage_tips.rst +++ b/docs/cookbook/usage_tips.rst @@ -2,7 +2,7 @@ API Usage Tips ############## -Below is a list of helpful tips when using the Shotgun API. We have tried to make the API very +Below is a list of helpful tips when using the ShotGrid API. We have tried to make the API very simple to use with predictable results while remaining a powerful tool to integrate with your pipeline. However, there's always a couple of things that crop up that our users might not be aware of. Those are the types of things you'll find below. We'll be adding to this document over @@ -28,7 +28,7 @@ Don't:: *************** Multi-threading *************** -The Shotgun API is not thread-safe. If you want to do threading we strongly suggest that you use +The ShotGrid API is not thread-safe. If you want to do threading we strongly suggest that you use one connection object per thread and not share the connection. ************* @@ -83,7 +83,7 @@ Then when you're writing scripts, you don't need to worry about remembering whic import shotgun_api3 import studio_globals - sg = Shotgun('https://mystudio.shotgunstudio.com', 'script_name', '0123456789abcdef0123456789abcdef0123456') + sg = Shotgun('https://my-site.shotgrid.autodesk.com', 'script_name', '0123456789abcdef0123456789abcdef0123456') result = sg.find(studio_globals.ENTITY_WIDGET, filters=[['sg_status_list', 'is', 'ip']], fields=['code', 'sg_shot']) @@ -103,7 +103,7 @@ example of a field that resides on the connection entity between Playlists and V connection entity is appropriately called `PlaylistVersionConnection`. Because any Version can exist in multiple Playlists, the sort order isn't specific to the Version, it's specific to each _instance_ of the Version in a Playlist. These instances are tracked using connection -entities in Shtogun and are accessible just like any other entity type in Shotgun. +entities in Shtogun and are accessible just like any other entity type in ShotGrid. To find information about your Versions in the Playlist "Director Review" (let's say it has an ``id`` of 4). We'd run a query like so:: @@ -174,7 +174,7 @@ entity. The field we are interested on the Version is ``code``. Put those togeth riend the dot and we have ``version.Version.code``. ******************************************* -Shotgun UI fields not available via the API +ShotGrid UI fields not available via the API ******************************************* Summary type fields like Query Fields and Pipeline Step summary fields are currently only available @@ -242,14 +242,14 @@ To see the logging output in stdout, define a streamhandler in your script:: import shotgun_api3 as shotgun logging.basicConfig(level=logging.DEBUG) -To write logging output from the shotgun API to a file, define a file handler in your script:: +To write logging output from the ShotGrid API to a file, define a file handler in your script:: import logging import shotgun_api3 as shotgun logging.basicConfig(level=logging.DEBUG, filename='/path/to/your/log') To suppress the logging output from the API in a script which uses logging, set the level of the -Shotgun logger to a higher level:: +ShotGrid logger to a higher level:: import logging import shotgun_api3 as shotgun @@ -263,7 +263,7 @@ Optimizations Combining Related Queries ========================= Reducing round-trips for data via the API can significantly improve the speed of your application. -Much like "Bubble Fields" / "Field Hopping" in the UI, we can poll Shotgun for data on the fields +Much like "Bubble Fields" / "Field Hopping" in the UI, we can poll ShotGrid for data on the fields of entities linked to our main query, both as a part of the query parameters as well as in the data returned. diff --git a/docs/index.rst b/docs/index.rst index 93ad51cf1..96ac44db0 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,5 +1,5 @@ ################### -Shotgun Python API3 +ShotGrid Python API3 ################### Release |version|. (:ref:`Installation `) @@ -8,15 +8,15 @@ Release |version|. (:ref:`Installation `) -Shotgun provides a simple Python-based API for accessing Shotgun and integrating with other tools. -The Shotgun API allows users to integrate their tools with Shotgun very easily. Using this simple -but powerful python module , you can quickly get your scripts integrated with Shotgun's CRUD-based +ShotGrid provides a simple Python-based API for accessing ShotGrid and integrating with other tools. +The ShotGrid API allows users to integrate their tools with ShotGrid very easily. Using this simple +but powerful python module , you can quickly get your scripts integrated with ShotGrid's CRUD-based API. Because the needs of every studio can prove to be very different, we don't include a lot of "automation" or "smarts" in our API. We have kept it pretty low-level and leave most of those decisions to you. The API is powerful enough you can write your own "smarts" in a wrapper on top -of the Shotgun API. +of the ShotGrid API. .. _pythonoverviewvideo: @@ -33,7 +33,7 @@ Quicktimes, etc. **Example**:: - sg = shotgun_api3.Shotgun("https://piedpiper.shotgunstudio.com", + sg = shotgun_api3.Shotgun("https://my-site.shotgrid.autodesk.com", login="rhendriks", password="c0mPre$Hi0n") sg.find("Shot", filters=[["sg_status_list", "is", "ip"]], fields=["code", "sg_status_list"]) diff --git a/docs/installation.rst b/docs/installation.rst index b2065bfd4..844b35170 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -6,16 +6,16 @@ Installation Minimum Requirements ******************** -- Shotgun server v5.4.14 or higher for API v3.0.16+. -- Shotgun server v2.4.12 or higher for API v3.0.8. -- Shotgun server v1.8 or higher for API v3.0.7. +- ShotGrid server v5.4.14 or higher for API v3.0.16+. +- ShotGrid server v2.4.12 or higher for API v3.0.8. +- ShotGrid server v1.8 or higher for API v3.0.7. - Python 2.7 or Python 3.7 .. note:: - Some features of the API are only supported by more recent versions of the Shotgun server. + Some features of the API are only supported by more recent versions of the ShotGrid server. These features are added to the Python API in a backwards compatible way so that existing scripts will continue to function as expected. Accessing a method that is not supported for - your version of Shotgun will raise an appropriate exception. In general, we attempt to + your version of ShotGrid will raise an appropriate exception. In general, we attempt to document these where possible. ****************************** diff --git a/docs/reference.rst b/docs/reference.rst index 8ef3bf704..47a27e9c6 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -27,7 +27,7 @@ Shotgun() :show-inheritance: *************** -Shotgun Methods +ShotGrid Methods *************** The majority of functionality is contained within the :class:`~shotgun_api3.Shotgun` class. @@ -91,7 +91,7 @@ The documentation for all of the methods you'll need in your scripts lives in he Shotgun.followers Shotgun.following -.. rubric:: Working with the Shotgun Schema and Preferences +.. rubric:: Working with the ShotGrid Schema and Preferences .. autosummary:: :nosignatures: @@ -109,7 +109,7 @@ The documentation for all of the methods you'll need in your scripts lives in he Connection & Authentication =========================== -These methods are used for connecting and authenticating with your Shotgun server. Most of +These methods are used for connecting and authenticating with your ShotGrid server. Most of this is done automatically when you instantiate your instance. But if you need finer-grain control, these methods are available. @@ -161,7 +161,7 @@ Methods that handle uploading and downloading files including thumbnails. Activity Stream =============== -Methods that relate to the activity stream and following of entities in Shotgun. +Methods that relate to the activity stream and following of entities in ShotGrid. .. automethod:: Shotgun.activity_stream_read .. automethod:: Shotgun.follow @@ -169,10 +169,10 @@ Methods that relate to the activity stream and following of entities in Shotgun. .. automethod:: Shotgun.followers .. automethod:: Shotgun.following -Working with the Shotgun Schema +Working with the ShotGrid Schema =============================== -Methods allow you to introspect and modify the Shotgun schema. +Methods allow you to introspect and modify the ShotGrid schema. .. automethod:: Shotgun.schema_entity_read .. automethod:: Shotgun.schema_field_read @@ -187,7 +187,7 @@ Methods allow you to introspect and modify the Shotgun schema. Exceptions ********** -These are the various exceptions that the Shotgun API will raise. +These are the various exceptions that the ShotGrid API will raise. .. autoclass:: shotgun_api3.ShotgunError :show-inheritance: @@ -471,7 +471,7 @@ Valid Operators By Data Type Additional Filter Presets ========================= -As of Shotgun version 7.0 it is possible to also use filter presets. These presets provide a simple +As of ShotGrid version 7.0 it is possible to also use filter presets. These presets provide a simple way to specify powerful query filters that would otherwise be costly and difficult to craft using traditional filters. @@ -626,7 +626,7 @@ date_time :range: Year must be >= 1970 .. note:: - Datetimes are stored as UTC on the server. The Shotgun API is configured to automatically + Datetimes are stored as UTC on the server. The ShotGrid API is configured to automatically convert between client local time and UTC. This can be overridden. duration @@ -818,7 +818,7 @@ There are three possible states for values returned by an ``image`` field: .. note:: Other upcoming features are likely to require the use of other transient thumbnails. For this reason, it is highly recommended to use the prefix part of the placeholder path - (e.g. https://yoursubdomain.shotgunstudio.com/images/status/transient/) + (e.g. https://my-site.shotgrid.autodesk.com/images/status/transient/) to detect any transient URLs rather than use the full path of the thumbnail. .. _event_types: @@ -827,15 +827,15 @@ There are three possible states for values returned by an ``image`` field: Event Types *********** -Whenever a user makes a change to any data in Shotgun, an event log entry record is created, -capturing the value before and after. Shotgun also logs some additional useful events that help keep -track of various activity on your Shotgun instance. +Whenever a user makes a change to any data in ShotGrid, an event log entry record is created, +capturing the value before and after. ShotGrid also logs some additional useful events that help keep +track of various activity on your ShotGrid instance. Event-based Triggers ==================== Events are particularlly useful when used in conjunction with a trigger framework like the -`Shotgun Event Daemon `_. This allows you to +`ShotGrid Event Daemon `_. This allows you to write plug-ins that watch for certain types of events and then run code when they occur. Structure of Event Types @@ -845,11 +845,11 @@ The basic structure of event types is broken into 3 parts: ``Application_EntityType_Action`` -- ``Application``: Is always "Shotgun" for events automatically created by the Shotgun server. - Other Shotgun products may use their name in here, for example, Toolkit has its own events +- ``Application``: Is always "Shotgun" for events automatically created by the ShotGrid server. + Other ShotGrid products may use their name in here, for example, Toolkit has its own events that it logs and the application portion is identified by "Toolkit". If you decide to use the EventLogEntry entity to log events for your scripts or tools, you would use your tool name here. -- ``EntityType``: This is the entity type in Shotgun that was acted upon (eg. Shot, Asset, etc.) +- ``EntityType``: This is the entity type in ShotGrid that was acted upon (eg. Shot, Asset, etc.) - ``Action``: The general action that was taken. (eg. New, Change, Retirement, Revival) @@ -867,14 +867,14 @@ deleted, and revived. They follow this pattern: Additional Event Types ====================== -These are _some_ of the additional event types that are logged by Shotgun: +These are _some_ of the additional event types that are logged by ShotGrid: - ``Shotgun_Attachment_View``: an Attachment (file) was viewed by a user. - ``Shotgun_Reading_Change``: a threaded entity has been marked read or unread. For example, a Note was read by a user. The readings are unique to the entity<->user connection so when a Note is read by user "joe" it may still be unread by user "jane". -- ``Shotgun_User_Login``: a user logged in to Shotgun. -- ``Shotgun_User_Logout``: a user logged out of Shotgun. +- ``Shotgun_User_Login``: a user logged in to ShotGrid. +- ``Shotgun_User_Logout``: a user logged out of ShotGrid. Custom Event Types @@ -882,10 +882,10 @@ Custom Event Types Since ``EventLogEntries`` are entities themselves, you can create them using the API just like any other entity type. As mentioned previously, if you'd like to have your scripts or tools log to -the Shotgun event log, simply devise a thoughtful naming structure for your event types and +the ShotGrid event log, simply devise a thoughtful naming structure for your event types and create the EventLogEntry as needed following the usual methods for creating entities via the API. -Again, other Shotgun products like Toolkit use event logs this way. +Again, other ShotGrid products like Toolkit use event logs this way. .. note:: EventLogEntries cannot be updated or deleted (that would defeat the purpose of course). @@ -893,13 +893,13 @@ Again, other Shotgun products like Toolkit use event logs this way. Performance =========== -Event log database tables can get large very quickly. While Shotgun does very well with event logs +Event log database tables can get large very quickly. While ShotGrid does very well with event logs that get into the millions of records, there's an inevitable degradation of performance for pages that display them in the web application as well as any API queries for events when they get too big. This volume of events is not the norm, but can be reached if your server expereinces high usage. -This **does not** mean your Shotgun server performance will suffer in general, just any pages that +This **does not** mean your ShotGrid server performance will suffer in general, just any pages that are specifically displaying EventLogEntries in the web application, or API queries on the event log that are run. We are always looking for ways to improve this in the future. If you have any immediate concerns, please `reach out to our support team `_ @@ -928,7 +928,7 @@ In the case that both this environment variable and the config's ``rpc_attempt_i Localization ************ -The Shotgun API offers the ability to return localized display names in the current user's language. +The ShotGrid API offers the ability to return localized display names in the current user's language. Requests made from script/API users are localized in the site settings. This functionality is currently supported by the methods ``Shotgun.schema_entity_read``, ``Shotgun.schema_field_read``, and ``Shotgun.schema_read``. diff --git a/shotgun_api3/lib/README.md b/shotgun_api3/lib/README.md index a3aa1f0e5..e8fc31cd6 100644 --- a/shotgun_api3/lib/README.md +++ b/shotgun_api3/lib/README.md @@ -6,7 +6,7 @@ Some third-party modules are bundled with `python-api` inside lib. ### httplib2 -`httplib2` is used to make http connections to the shotgun server. We bundle both python2 and python3 compatible versions since httplib2 chose to maintain parallel versions of the module for python 2 and 3 compatibility. +`httplib2` is used to make http connections to the ShotGrid server. We bundle both python2 and python3 compatible versions since httplib2 chose to maintain parallel versions of the module for python 2 and 3 compatibility. The version of `httplib2` bundled should be updated manually, however its version is included in the unused `shotgun_api3/lib/requirements.txt` to allow Github's automated CVE notifications to work. @@ -29,7 +29,7 @@ Six is a Python 2/3 compatibility library. In python-api, it's used to make sim The version of `six` bundled should be updated manually, however its version is included in the unused `shotgun_api3/lib/requirements.txt` to allow Github's automated CVE notifications to work. -## Shotgun Modules +## ShotGrid Modules ### sgsix diff --git a/software_credits b/software_credits index 7e5c0be7f..dd1ef31da 100644 --- a/software_credits +++ b/software_credits @@ -1,4 +1,4 @@ -The Shotgun Python API uses the following software. Thanks to their creators, license information below. +The ShotGrid Python API uses the following software. Thanks to their creators, license information below. ============================== PYTHON ============================== diff --git a/tests/example_config b/tests/example_config index 451b10dd6..01501d23d 100644 --- a/tests/example_config +++ b/tests/example_config @@ -13,8 +13,8 @@ [SERVER_INFO] -# Full url to the Shotgun server server -# e.g. http://my-company.shotgunstudio.com +# Full url to the ShotGrid server server +# e.g. https://my-site.shotgrid.autodesk.com # be careful to not end server_url with a "/", or some tests may fail server_url : http://0.0.0.0:3000 # script name as key as listed in admin panel for your server diff --git a/tests/mockgun/schema.pickle b/tests/mockgun/schema.pickle index e2e7c8cc6..20ef8ee02 100644 --- a/tests/mockgun/schema.pickle +++ b/tests/mockgun/schema.pickle @@ -86870,7 +86870,7 @@ p42730 I01 sS'value' p42731 -S'File field to contain the uploaded movie file. Used for playback of lower resolution movie media stored in Shotgun.' +S'File field to contain the uploaded movie file. Used for playback of lower resolution movie media stored in ShotGrid.' p42732 sssS'user' p42733 From 9751d808063655c2b5e2763f9c678869d73fd43b Mon Sep 17 00:00:00 2001 From: Ariel Calzada Date: Tue, 3 Jan 2023 15:02:14 -0500 Subject: [PATCH 013/125] SG-29417 [Python API Documentation] Update Python version requirements (#277) * Remove python 2.7 from readme and docs sections "Minimum requirements" * Remove references to old Shotgun Server versions. --- README.md | 3 +-- docs/installation.rst | 5 +---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 12b9f6f1b..c587cef42 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,7 @@ The latest version can always be found at http://github.com/shotgunsoftware/pyth ## Minimum Requirements -* ShotGrid server v2.4.12+. -* Python v2.7 or v3.7 +* Python v3.7 ## Documentation Tutorials and detailed documentation about the Python API are available at http://developer.shotgridsoftware.com/python-api). diff --git a/docs/installation.rst b/docs/installation.rst index 844b35170..c40f61560 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -6,10 +6,7 @@ Installation Minimum Requirements ******************** -- ShotGrid server v5.4.14 or higher for API v3.0.16+. -- ShotGrid server v2.4.12 or higher for API v3.0.8. -- ShotGrid server v1.8 or higher for API v3.0.7. -- Python 2.7 or Python 3.7 +- Python 3.7 .. note:: Some features of the API are only supported by more recent versions of the ShotGrid server. From 1da538592f6dc15a895242b818a2bb18c0a17703 Mon Sep 17 00:00:00 2001 From: Norberto Moreno Date: Thu, 5 Jan 2023 16:12:48 -0500 Subject: [PATCH 014/125] packaging for the v3.3.5 release. (#278) Packaging for the v3.3.5 release. --- HISTORY.rst | 7 +++++++ setup.py | 2 +- shotgun_api3/shotgun.py | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 7f65b0c4f..6cc547cbb 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -4,6 +4,13 @@ ShotGrid Python API Changelog Here you can see the full list of changes between each Python API release. +v3.3.5 (2023 Jan 5) +==================== +- Add "Setting Up Your Environment with the Python API" to Python Docs (python-api docs). +- [Python API Documentation] Update Python version requirements. +- Rename Shotgun to Shotgrid in every about text like tk-multi-demo git repository. +- Rename Shotgun servers to ShotGrid servers in the documentation. + v3.3.4 (2022 June 9) ==================== - Adds Retries on 503 Errors when uploading to S3. diff --git a/setup.py b/setup.py index 923da82e4..6462d1781 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ setup( name='shotgun_api3', - version='3.3.4', + version='3.3.5', description='Shotgun Python API ', long_description=readme, author='Shotgun Software', diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index 928a1e3c1..120d0489a 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -122,7 +122,7 @@ def _is_mimetypes_broken(): # ---------------------------------------------------------------------------- # Version -__version__ = "3.3.4" +__version__ = "3.3.5" # ---------------------------------------------------------------------------- # Errors From 86f64f32c03852d73601b53c672f172e84b0575f Mon Sep 17 00:00:00 2001 From: Carlos Villavicencio <123113322+carlos-villavicencio-adsk@users.noreply.github.com> Date: Tue, 31 Jan 2023 16:51:10 -0500 Subject: [PATCH 015/125] SG-21729 Update docs for entity fields. (#279) * SG-21729 Update docs for entity fields. * Update docs/cookbook/usage_tips.rst Co-authored-by: Shayna Duguid --------- Co-authored-by: Shayna Duguid --- docs/cookbook/usage_tips.rst | 17 ++++++++++++----- shotgun_api3/shotgun.py | 19 +++++++++++++++++-- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/docs/cookbook/usage_tips.rst b/docs/cookbook/usage_tips.rst index 0a0fb5d5e..389eb5227 100644 --- a/docs/cookbook/usage_tips.rst +++ b/docs/cookbook/usage_tips.rst @@ -31,13 +31,15 @@ Multi-threading The ShotGrid API is not thread-safe. If you want to do threading we strongly suggest that you use one connection object per thread and not share the connection. +.. _entity-fields: + ************* Entity Fields ************* -When you do a :meth:`~shotgun_api3.Shotgun.find` call that returns a field of type entity or -multi-entity (for example the 'assets' column on Shot), the entities are returned in a standard -dictionary:: +When you do a :meth:`~shotgun_api3.Shotgun.find` or a :meth:`~shotgun_api3.Shotgun.create` call +that returns a field of type **entity** or **multi-entity** (for example the 'Assets' column on Shot), +the entities are returned in a standard dictionary:: {'type': 'Asset', 'name': 'redBall', 'id': 1} @@ -260,6 +262,8 @@ ShotGrid logger to a higher level:: Optimizations ************* +.. _combining-related-queries: + Combining Related Queries ========================= Reducing round-trips for data via the API can significantly improve the speed of your application. @@ -285,7 +289,7 @@ With "field hopping" you can combine these queries into:: sg_shots = sg.find("Shot", [['project.Project.name', 'is', project_name]], ['code']) As you can see above, the syntax is to use "``.``" dot notation, joining field names to entity -types in a chain. In this example we start with the field ``project`` on the Shot entity, then +types in a chain. In this example we start with the field ``project`` on the ``Shot`` entity, then specify we're looking for the "name" field on the Project entity by specifying ``Project.name``. Now that we've demonstrated querying using dot notation, let's take a look at returning linked data @@ -296,7 +300,10 @@ by adding the status of each Sequence entity associated with each Shot in our pr sg_shots = sg.find("Shot", [['project.Project.name', 'is', project_name]], ['code', 'sg_sequence.Sequence.sg_status_list']) +The previous examples use the :meth:`~shotgun_api3.Shotgun.find` method. However, it's also applicable +to the :meth:`~shotgun_api3.Shotgun.create` method. + .. note:: - Due to performance concerns with deep linking, we only support using dot syntax chains for + Due to performance concerns with deep linking, we only support using dot notation chains for single-entity relationships. This means that if you try to pull data through a multi-entity field you will not get the desired result. \ No newline at end of file diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index 120d0489a..9b397a7f0 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -874,6 +874,9 @@ def find_one(self, entity_type, filters, fields=None, order=None, filter_operato :ref:`additional_filter_presets` :returns: Dictionary representing a single matching entity with the requested fields, and the defaults ``"id"`` and ``"type"`` which are always included. + + .. seealso:: :ref:`entity-fields` + :rtype: dict """ @@ -910,7 +913,7 @@ def find(self, entity_type, filters, fields=None, order=None, filter_operator=No {'code': 'Wet Gopher', 'id': 149, 'sg_asset_type': 'Character', 'type': 'Asset'}] You can drill through single entity links to filter on fields or display linked fields. - This is often called "deep linking" or using "dot syntax". + This is often called "deep linking" or using "dot notation". .. seealso:: :ref:`filter_syntax` @@ -933,10 +936,13 @@ def find(self, entity_type, filters, fields=None, order=None, filter_operator=No :param str entity_type: Shotgun entity type to find. :param list filters: list of filters to apply to the query. - .. seealso:: :ref:`filter_syntax` + .. seealso:: :ref:`filter_syntax`, :ref:`combining-related-queries` :param list fields: Optional list of fields to include in each entity record returned. Defaults to ``["id"]``. + + .. seealso:: :ref:`combining-related-queries` + :param list order: Optional list of dictionaries defining how to order the results of the query. Each dictionary contains the ``field_name`` to order by and the ``direction`` to sort:: @@ -970,6 +976,9 @@ def find(self, entity_type, filters, fields=None, order=None, filter_operator=No :ref:`additional_filter_presets` :returns: list of dictionaries representing each entity with the requested fields, and the defaults ``"id"`` and ``"type"`` which are always included. + + .. seealso:: :ref:`entity-fields` + :rtype: list """ @@ -1332,10 +1341,16 @@ def create(self, entity_type, data, return_fields=None): to the server automatically. :param list return_fields: Optional list of additional field values to return from the new entity. Defaults to ``id`` field. + + .. seealso:: :ref:`combining-related-queries` + :returns: Shotgun entity dictionary containing the field/value pairs of all of the fields set from the ``data`` parameter as well as the defaults ``type`` and ``id``. If any additional fields were provided using the ``return_fields`` parameter, these would be included as well. + + .. seealso:: :ref:`entity-fields` + :rtype: dict """ From 573e28882f4ad11a571035e74f66bffcedc7b1b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sauv=C3=A9?= <119680820+dsauve-adsk@users.noreply.github.com> Date: Tue, 21 Mar 2023 11:16:26 -0400 Subject: [PATCH 016/125] SG-30199 Fix typo (#280) --- shotgun_api3/shotgun.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index 9b397a7f0..d648eb350 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -857,7 +857,7 @@ def find_one(self, entity_type, filters, fields=None, order=None, filter_operato and ``"any"``. These are just another way of defining if the query is an AND or OR query. Defaults to ``"all"``. :param bool retired_only: Optional boolean when ``True`` will return only entities that have - been retried. Defaults to ``False`` which returns only entities which have not been + been retired. Defaults to ``False`` which returns only entities which have not been retired. There is no option to return both retired and non-retired entities in the same query. :param bool include_archived_projects: Optional boolean flag to include entities whose projects @@ -959,7 +959,7 @@ def find(self, entity_type, filters, fields=None, order=None, filter_operator=No parameter to control how your query results are paged. Defaults to ``0`` which returns all entities that match. :param bool retired_only: Optional boolean when ``True`` will return only entities that have - been retried. Defaults to ``False`` which returns only entities which have not been + been retired. Defaults to ``False`` which returns only entities which have not been retired. There is no option to return both retired and non-retired entities in the same query. :param bool include_archived_projects: Optional boolean flag to include entities whose projects From ae7695455c5ce826e79872dd344f01cddd9d39a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sauv=C3=A9?= <119680820+dsauve-adsk@users.noreply.github.com> Date: Tue, 21 Mar 2023 11:16:50 -0400 Subject: [PATCH 017/125] SG-30223 Fix incorrect hint (#281) --- shotgun_api3/shotgun.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index d648eb350..715bfafc4 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -3340,7 +3340,7 @@ def schema(self, entity_type): .. deprecated:: 3.0.0 Use :meth:`~shotgun_api3.Shotgun.schema_field_read` instead. """ - raise ShotgunError("Deprecated: use schema_field_read('type':'%s') instead" % entity_type) + raise ShotgunError("Deprecated: use schema_field_read('%s') instead" % entity_type) def entity_types(self): """ From 7651d8a01dfead5c2a2174cd26656c5d2f22b8ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sauv=C3=A9?= <119680820+dsauve-adsk@users.noreply.github.com> Date: Tue, 21 Mar 2023 11:17:17 -0400 Subject: [PATCH 018/125] SG-30223 Reformat code examples to prevent text overflow (#282) --- shotgun_api3/shotgun.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index 715bfafc4..ef6d7c5a5 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -850,7 +850,10 @@ def find_one(self, entity_type, filters, fields=None, order=None, filter_operato Defaults to ``["id"]``. :param int order: Optional list of fields to order the results by. List has the format:: - [{'field_name':'foo', 'direction':'asc'}, {'field_name':'bar', 'direction':'desc'}] + [ + {'field_name':'foo', 'direction':'asc'}, + {'field_name':'bar', 'direction':'desc'} + ] Defaults to sorting by ``id`` in ascending order. :param str filter_operator: Operator to apply to the filters. Supported values are ``"all"`` @@ -865,7 +868,11 @@ def find_one(self, entity_type, filters, fields=None, order=None, filter_operato :param additional_filter_presets: Optional list of presets to further filter the result set, list has the form:: - [{"preset_name": , : , ... }] + [{ + "preset_name": , + : , + ... + }] Note that these filters are ANDed together and ANDed with the 'filter' argument. @@ -947,7 +954,10 @@ def find(self, entity_type, filters, fields=None, order=None, filter_operator=No query. Each dictionary contains the ``field_name`` to order by and the ``direction`` to sort:: - [{'field_name':'foo', 'direction':'asc'}, {'field_name':'bar', 'direction':'desc'}] + [ + {'field_name':'foo', 'direction':'asc'}, + {'field_name':'bar', 'direction':'desc'} + ] Defaults to sorting by ``id`` in ascending order. :param str filter_operator: Operator to apply to the filters. Supported values are ``"all"`` @@ -967,7 +977,11 @@ def find(self, entity_type, filters, fields=None, order=None, filter_operator=No :param additional_filter_presets: Optional list of presets to further filter the result set, list has the form:: - [{"preset_name": , : , ... }] + [{ + "preset_name": , + : , + ... + }] Note that these filters are ANDed together and ANDed with the 'filter' argument. From 091821fd458a7d74fc98c7a7558a2c0f87d48925 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Apr 2023 14:41:28 -0500 Subject: [PATCH 019/125] SG-29757 Bump certifi from 2020.06.20 to 2022.12.7 in /shotgun_api3/lib (#283) * Bump certifi from 2020.06.20 to 2022.12.7 in /shotgun_api3/lib Bumps [certifi](https://github.com/certifi/python-certifi) from 2020.06.20 to 2022.12.7. - [Release notes](https://github.com/certifi/python-certifi/releases) - [Commits](https://github.com/certifi/python-certifi/compare/2020.06.20...2022.12.07) --- updated-dependencies: - dependency-name: certifi dependency-type: direct:production ... Signed-off-by: dependabot[bot] * Upgrade lib --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Carlos Villavicencio --- shotgun_api3/lib/.gitignore | 1 + shotgun_api3/lib/certifi/__init__.py | 3 +- shotgun_api3/lib/certifi/cacert.pem | 2319 ++++++++++++-------------- shotgun_api3/lib/certifi/core.py | 74 +- shotgun_api3/lib/certifi/py.typed | 0 shotgun_api3/lib/requirements.txt | 2 +- 6 files changed, 1178 insertions(+), 1221 deletions(-) create mode 100644 shotgun_api3/lib/.gitignore create mode 100644 shotgun_api3/lib/certifi/py.typed diff --git a/shotgun_api3/lib/.gitignore b/shotgun_api3/lib/.gitignore new file mode 100644 index 000000000..2b25fa894 --- /dev/null +++ b/shotgun_api3/lib/.gitignore @@ -0,0 +1 @@ +*.dist-info \ No newline at end of file diff --git a/shotgun_api3/lib/certifi/__init__.py b/shotgun_api3/lib/certifi/__init__.py index 5d52a62e7..a3546f125 100644 --- a/shotgun_api3/lib/certifi/__init__.py +++ b/shotgun_api3/lib/certifi/__init__.py @@ -1,3 +1,4 @@ from .core import contents, where -__version__ = "2020.06.20" +__all__ = ["contents", "where"] +__version__ = "2022.12.07" diff --git a/shotgun_api3/lib/certifi/cacert.pem b/shotgun_api3/lib/certifi/cacert.pem index 0fd855f46..df9e4e3c7 100644 --- a/shotgun_api3/lib/certifi/cacert.pem +++ b/shotgun_api3/lib/certifi/cacert.pem @@ -28,36 +28,6 @@ DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== -----END CERTIFICATE----- -# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R2 -# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R2 -# Label: "GlobalSign Root CA - R2" -# Serial: 4835703278459682885658125 -# MD5 Fingerprint: 94:14:77:7e:3e:5e:fd:8f:30:bd:41:b0:cf:e7:d0:30 -# SHA1 Fingerprint: 75:e0:ab:b6:13:85:12:27:1c:04:f8:5f:dd:de:38:e4:b7:24:2e:fe -# SHA256 Fingerprint: ca:42:dd:41:74:5f:d0:b8:1e:b9:02:36:2c:f9:d8:bf:71:9d:a1:bd:1b:1e:fc:94:6f:5b:4c:99:f4:2c:1b:9e ------BEGIN CERTIFICATE----- -MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4G -A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNp -Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1 -MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEG -A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI -hvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6ErPL -v4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8 -eoLrvozps6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklq -tTleiDTsvHgMCJiEbKjNS7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzd -C9XZzPnqJworc5HGnRusyMvo4KD0L5CLTfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pa -zq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6CygPCm48CAwEAAaOBnDCB -mTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUm+IH -V2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5n -bG9iYWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG -3lm0mi3f3BmGLjANBgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4Gs -J0/WwbgcQ3izDJr86iw8bmEbTUsp9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO -291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu01yiPqFbQfXf5WRDLenVOavS -ot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG79G+dwfCMNYxd -AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7 -TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg== ------END CERTIFICATE----- - # Issuer: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited # Subject: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited # Label: "Entrust.net Premium 2048 Secure Server CA" @@ -155,112 +125,6 @@ eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0tHuu2guQOHXvgR1m 0vdXcDazv/wor3ElhVsT/h5/WrQ8 -----END CERTIFICATE----- -# Issuer: CN=GeoTrust Global CA O=GeoTrust Inc. -# Subject: CN=GeoTrust Global CA O=GeoTrust Inc. -# Label: "GeoTrust Global CA" -# Serial: 144470 -# MD5 Fingerprint: f7:75:ab:29:fb:51:4e:b7:77:5e:ff:05:3c:99:8e:f5 -# SHA1 Fingerprint: de:28:f4:a4:ff:e5:b9:2f:a3:c5:03:d1:a3:49:a7:f9:96:2a:82:12 -# SHA256 Fingerprint: ff:85:6a:2d:25:1d:cd:88:d3:66:56:f4:50:12:67:98:cf:ab:aa:de:40:79:9c:72:2d:e4:d2:b5:db:36:a7:3a ------BEGIN CERTIFICATE----- -MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT -MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i -YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG -EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg -R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9 -9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq -fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv -iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU -1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+ -bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW -MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA -ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l -uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn -Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS -tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF -PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un -hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV -5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw== ------END CERTIFICATE----- - -# Issuer: CN=GeoTrust Universal CA O=GeoTrust Inc. -# Subject: CN=GeoTrust Universal CA O=GeoTrust Inc. -# Label: "GeoTrust Universal CA" -# Serial: 1 -# MD5 Fingerprint: 92:65:58:8b:a2:1a:31:72:73:68:5c:b4:a5:7a:07:48 -# SHA1 Fingerprint: e6:21:f3:35:43:79:05:9a:4b:68:30:9d:8a:2f:74:22:15:87:ec:79 -# SHA256 Fingerprint: a0:45:9b:9f:63:b2:25:59:f5:fa:5d:4c:6d:b3:f9:f7:2f:f1:93:42:03:35:78:f0:73:bf:1d:1b:46:cb:b9:12 ------BEGIN CERTIFICATE----- -MIIFaDCCA1CgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJVUzEW -MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgVW5pdmVy -c2FsIENBMB4XDTA0MDMwNDA1MDAwMFoXDTI5MDMwNDA1MDAwMFowRTELMAkGA1UE -BhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xHjAcBgNVBAMTFUdlb1RydXN0 -IFVuaXZlcnNhbCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKYV -VaCjxuAfjJ0hUNfBvitbtaSeodlyWL0AG0y/YckUHUWCq8YdgNY96xCcOq9tJPi8 -cQGeBvV8Xx7BDlXKg5pZMK4ZyzBIle0iN430SppyZj6tlcDgFgDgEB8rMQ7XlFTT -QjOgNB0eRXbdT8oYN+yFFXoZCPzVx5zw8qkuEKmS5j1YPakWaDwvdSEYfyh3peFh -F7em6fgemdtzbvQKoiFs7tqqhZJmr/Z6a4LauiIINQ/PQvE1+mrufislzDoR5G2v -c7J2Ha3QsnhnGqQ5HFELZ1aD/ThdDc7d8Lsrlh/eezJS/R27tQahsiFepdaVaH/w -mZ7cRQg+59IJDTWU3YBOU5fXtQlEIGQWFwMCTFMNaN7VqnJNk22CDtucvc+081xd -VHppCZbW2xHBjXWotM85yM48vCR85mLK4b19p71XZQvk/iXttmkQ3CgaRr0BHdCX -teGYO8A3ZNY9lO4L4fUorgtWv3GLIylBjobFS1J72HGrH4oVpjuDWtdYAVHGTEHZ -f9hBZ3KiKN9gg6meyHv8U3NyWfWTehd2Ds735VzZC1U0oqpbtWpU5xPKV+yXbfRe -Bi9Fi1jUIxaS5BZuKGNZMN9QAZxjiRqf2xeUgnA3wySemkfWWspOqGmJch+RbNt+ -nhutxx9z3SxPGWX9f5NAEC7S8O08ni4oPmkmM8V7AgMBAAGjYzBhMA8GA1UdEwEB -/wQFMAMBAf8wHQYDVR0OBBYEFNq7LqqwDLiIJlF0XG0D08DYj3rWMB8GA1UdIwQY -MBaAFNq7LqqwDLiIJlF0XG0D08DYj3rWMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG -9w0BAQUFAAOCAgEAMXjmx7XfuJRAyXHEqDXsRh3ChfMoWIawC/yOsjmPRFWrZIRc -aanQmjg8+uUfNeVE44B5lGiku8SfPeE0zTBGi1QrlaXv9z+ZhP015s8xxtxqv6fX -IwjhmF7DWgh2qaavdy+3YL1ERmrvl/9zlcGO6JP7/TG37FcREUWbMPEaiDnBTzyn -ANXH/KttgCJwpQzgXQQpAvvLoJHRfNbDflDVnVi+QTjruXU8FdmbyUqDWcDaU/0z -uzYYm4UPFd3uLax2k7nZAY1IEKj79TiG8dsKxr2EoyNB3tZ3b4XUhRxQ4K5RirqN -Pnbiucon8l+f725ZDQbYKxek0nxru18UGkiPGkzns0ccjkxFKyDuSN/n3QmOGKja -QI2SJhFTYXNd673nxE0pN2HrrDktZy4W1vUAg4WhzH92xH3kt0tm7wNFYGm2DFKW -koRepqO1pD4r2czYG0eq8kTaT/kD6PAUyz/zg97QwVTjt+gKN02LIFkDMBmhLMi9 -ER/frslKxfMnZmaGrGiR/9nmUxwPi1xpZQomyB40w11Re9epnAahNt3ViZS82eQt -DF4JbAiXfKM9fJP/P6EUp8+1Xevb2xzEdt+Iub1FBZUbrvxGakyvSOPOrg/Sfuvm -bJxPgWp6ZKy7PtXny3YuxadIwVyQD8vIP/rmMuGNG2+k5o7Y+SlIis5z/iw= ------END CERTIFICATE----- - -# Issuer: CN=GeoTrust Universal CA 2 O=GeoTrust Inc. -# Subject: CN=GeoTrust Universal CA 2 O=GeoTrust Inc. -# Label: "GeoTrust Universal CA 2" -# Serial: 1 -# MD5 Fingerprint: 34:fc:b8:d0:36:db:9e:14:b3:c2:f2:db:8f:e4:94:c7 -# SHA1 Fingerprint: 37:9a:19:7b:41:85:45:35:0c:a6:03:69:f3:3c:2e:af:47:4f:20:79 -# SHA256 Fingerprint: a0:23:4f:3b:c8:52:7c:a5:62:8e:ec:81:ad:5d:69:89:5d:a5:68:0d:c9:1d:1c:b8:47:7f:33:f8:78:b9:5b:0b ------BEGIN CERTIFICATE----- -MIIFbDCCA1SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBHMQswCQYDVQQGEwJVUzEW -MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVy -c2FsIENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMjkwMzA0MDUwMDAwWjBHMQswCQYD -VQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1 -c3QgVW5pdmVyc2FsIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC -AQCzVFLByT7y2dyxUxpZKeexw0Uo5dfR7cXFS6GqdHtXr0om/Nj1XqduGdt0DE81 -WzILAePb63p3NeqqWuDW6KFXlPCQo3RWlEQwAx5cTiuFJnSCegx2oG9NzkEtoBUG -FF+3Qs17j1hhNNwqCPkuwwGmIkQcTAeC5lvO0Ep8BNMZcyfwqph/Lq9O64ceJHdq -XbboW0W63MOhBW9Wjo8QJqVJwy7XQYci4E+GymC16qFjwAGXEHm9ADwSbSsVsaxL -se4YuU6W3Nx2/zu+z18DwPw76L5GG//aQMJS9/7jOvdqdzXQ2o3rXhhqMcceujwb -KNZrVMaqW9eiLBsZzKIC9ptZvTdrhrVtgrrY6slWvKk2WP0+GfPtDCapkzj4T8Fd -IgbQl+rhrcZV4IErKIM6+vR7IVEAvlI4zs1meaj0gVbi0IMJR1FbUGrP20gaXT73 -y/Zl92zxlfgCOzJWgjl6W70viRu/obTo/3+NjN8D8WBOWBFM66M/ECuDmgFz2ZRt -hAAnZqzwcEAJQpKtT5MNYQlRJNiS1QuUYbKHsu3/mjX/hVTK7URDrBs8FmtISgoc -QIgfksILAAX/8sgCSqSqqcyZlpwvWOB94b67B9xfBHJcMTTD7F8t4D1kkCLm0ey4 -Lt1ZrtmhN79UNdxzMk+MBB4zsslG8dhcyFVQyWi9qLo2CQIDAQABo2MwYTAPBgNV -HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAfBgNV -HSMEGDAWgBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAOBgNVHQ8BAf8EBAMCAYYwDQYJ -KoZIhvcNAQEFBQADggIBAGbBxiPz2eAubl/oz66wsCVNK/g7WJtAJDday6sWSf+z -dXkzoS9tcBc0kf5nfo/sm+VegqlVHy/c1FEHEv6sFj4sNcZj/NwQ6w2jqtB8zNHQ -L1EuxBRa3ugZ4T7GzKQp5y6EqgYweHZUcyiYWTjgAA1i00J9IZ+uPTqM1fp3DRgr -Fg5fNuH8KrUwJM/gYwx7WBr+mbpCErGR9Hxo4sjoryzqyX6uuyo9DRXcNJW2GHSo -ag/HtPQTxORb7QrSpJdMKu0vbBKJPfEncKpqA1Ihn0CoZ1Dy81of398j9tx4TuaY -T1U6U+Pv8vSfx3zYWK8pIpe44L2RLrB27FcRz+8pRPPphXpgY+RdM4kX2TGq2tbz -GDVyz4crL2MjhF2EjD9XoIj8mZEoJmmZ1I+XRL6O1UixpCgp8RW04eWe3fiPpm8m -1wk8OhwRDqZsN/etRIcsKMfYdIKz0G9KV7s1KSegi+ghp4dkNl3M2Basx7InQJJV -OCiNUW7dFGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH -6aLcr34YEoP9VhdBLtUpgn2Z9DH2canPLAEnpQW5qrJITirvn5NSUZU8UnOOVkwX -QMAJKOSLakhT2+zNVVXxxvjpoixMptEmX36vWkzaH6byHCx+rgIW0lbQL1dTR+iS ------END CERTIFICATE----- - # Issuer: CN=AAA Certificate Services O=Comodo CA Limited # Subject: CN=AAA Certificate Services O=Comodo CA Limited # Label: "Comodo AAA Services root" @@ -294,48 +158,6 @@ l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3 smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== -----END CERTIFICATE----- -# Issuer: CN=QuoVadis Root Certification Authority O=QuoVadis Limited OU=Root Certification Authority -# Subject: CN=QuoVadis Root Certification Authority O=QuoVadis Limited OU=Root Certification Authority -# Label: "QuoVadis Root CA" -# Serial: 985026699 -# MD5 Fingerprint: 27:de:36:fe:72:b7:00:03:00:9d:f4:f0:1e:6c:04:24 -# SHA1 Fingerprint: de:3f:40:bd:50:93:d3:9b:6c:60:f6:da:bc:07:62:01:00:89:76:c9 -# SHA256 Fingerprint: a4:5e:de:3b:bb:f0:9c:8a:e1:5c:72:ef:c0:72:68:d6:93:a2:1c:99:6f:d5:1e:67:ca:07:94:60:fd:6d:88:73 ------BEGIN CERTIFICATE----- -MIIF0DCCBLigAwIBAgIEOrZQizANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJC -TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDElMCMGA1UECxMcUm9vdCBDZXJ0 -aWZpY2F0aW9uIEF1dGhvcml0eTEuMCwGA1UEAxMlUXVvVmFkaXMgUm9vdCBDZXJ0 -aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wMTAzMTkxODMzMzNaFw0yMTAzMTcxODMz -MzNaMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUw -IwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVR -dW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG -9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv2G1lVO6V/z68mcLOhrfEYBklbTRvM16z/Yp -li4kVEAkOPcahdxYTMukJ0KX0J+DisPkBgNbAKVRHnAEdOLB1Dqr1607BxgFjv2D -rOpm2RgbaIr1VxqYuvXtdj182d6UajtLF8HVj71lODqV0D1VNk7feVcxKh7YWWVJ -WCCYfqtffp/p1k3sg3Spx2zY7ilKhSoGFPlU5tPaZQeLYzcS19Dsw3sgQUSj7cug -F+FxZc4dZjH3dgEZyH0DWLaVSR2mEiboxgx24ONmy+pdpibu5cxfvWenAScOospU -xbF6lR1xHkopigPcakXBpBlebzbNw6Kwt/5cOOJSvPhEQ+aQuwIDAQABo4ICUjCC -Ak4wPQYIKwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwczovL29jc3AucXVv -dmFkaXNvZmZzaG9yZS5jb20wDwYDVR0TAQH/BAUwAwEB/zCCARoGA1UdIASCAREw -ggENMIIBCQYJKwYBBAG+WAABMIH7MIHUBggrBgEFBQcCAjCBxxqBxFJlbGlhbmNl -IG9uIHRoZSBRdW9WYWRpcyBSb290IENlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBh -c3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFy -ZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRpb24gcHJh -Y3RpY2VzLCBhbmQgdGhlIFF1b1ZhZGlzIENlcnRpZmljYXRlIFBvbGljeS4wIgYI -KwYBBQUHAgEWFmh0dHA6Ly93d3cucXVvdmFkaXMuYm0wHQYDVR0OBBYEFItLbe3T -KbkGGew5Oanwl4Rqy+/fMIGuBgNVHSMEgaYwgaOAFItLbe3TKbkGGew5Oanwl4Rq -y+/foYGEpIGBMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1p -dGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYD -VQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggQ6tlCL -MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAitQUtf70mpKnGdSk -fnIYj9lofFIk3WdvOXrEql494liwTXCYhGHoG+NpGA7O+0dQoE7/8CQfvbLO9Sf8 -7C9TqnN7Az10buYWnuulLsS/VidQK2K6vkscPFVcQR0kvoIgR13VRH56FmjffU1R -cHhXHTMe/QKZnAzNCgVPx7uOpHX6Sm2xgI4JVrmcGmD+XcHXetwReNDWXcG31a0y -mQM6isxUJTkxgXsTIlG6Rmyhu576BGxJJnSP0nPrzDCi5upZIof4l/UO/erMkqQW -xFIY6iHOsfHmhIHluqmGKPJDWl0Snawe2ajlCmqnf6CHKc/yiU3U7MXi5nrQNiOK -SnQ2+Q== ------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" @@ -451,33 +273,6 @@ JRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ6rBK+1YWc26sTfcioU+tHXot RSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAiFL39vmwLAw== -----END CERTIFICATE----- -# Issuer: CN=Sonera Class2 CA O=Sonera -# Subject: CN=Sonera Class2 CA O=Sonera -# Label: "Sonera Class 2 Root CA" -# Serial: 29 -# MD5 Fingerprint: a3:ec:75:0f:2e:88:df:fa:48:01:4e:0b:5c:48:6f:fb -# SHA1 Fingerprint: 37:f7:6d:e6:07:7c:90:c5:b1:3e:93:1a:b7:41:10:b4:f2:e4:9a:27 -# SHA256 Fingerprint: 79:08:b4:03:14:c1:38:10:0b:51:8d:07:35:80:7f:fb:fc:f8:51:8a:00:95:33:71:05:ba:38:6b:15:3d:d9:27 ------BEGIN CERTIFICATE----- -MIIDIDCCAgigAwIBAgIBHTANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEP -MA0GA1UEChMGU29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MyIENBMB4XDTAx -MDQwNjA3Mjk0MFoXDTIxMDQwNjA3Mjk0MFowOTELMAkGA1UEBhMCRkkxDzANBgNV -BAoTBlNvbmVyYTEZMBcGA1UEAxMQU29uZXJhIENsYXNzMiBDQTCCASIwDQYJKoZI -hvcNAQEBBQADggEPADCCAQoCggEBAJAXSjWdyvANlsdE+hY3/Ei9vX+ALTU74W+o -Z6m/AxxNjG8yR9VBaKQTBME1DJqEQ/xcHf+Js+gXGM2RX/uJ4+q/Tl18GybTdXnt -5oTjV+WtKcT0OijnpXuENmmz/V52vaMtmdOQTiMofRhj8VQ7Jp12W5dCsv+u8E7s -3TmVToMGf+dJQMjFAbJUWmYdPfz56TwKnoG4cPABi+QjVHzIrviQHgCWctRUz2Ej -vOr7nQKV0ba5cTppCD8PtOFCx4j1P5iop7oc4HFx71hXgVB6XGt0Rg6DA5jDjqhu -8nYybieDwnPz3BjotJPqdURrBGAgcVeHnfO+oJAjPYok4doh28MCAwEAAaMzMDEw -DwYDVR0TAQH/BAUwAwEB/zARBgNVHQ4ECgQISqCqWITTXjwwCwYDVR0PBAQDAgEG -MA0GCSqGSIb3DQEBBQUAA4IBAQBazof5FnIVV0sd2ZvnoiYw7JNn39Yt0jSv9zil -zqsWuasvfDXLrNAPtEwr/IDva4yRXzZ299uzGxnq9LIR/WFxRL8oszodv7ND6J+/ -3DEIcbCdjdY0RzKQxmUk96BKfARzjzlvF4xytb1LyHr4e4PDKE6cCepnP7JnBBvD -FNr450kkkdAdavphOe9r5yF1BgfYErQhIHBCcYHaPJo2vqZbDWpsmh+Re/n570K6 -Tk6ezAyNlNzZRZxe7EJQY670XcSxEtzKO6gunRRaBXW37Ndj4ro1tgQIkejanZz2 -ZrUYrAqmVCY0M9IbwdR/GjqOC6oybtv8TyWf2TLHllpwrN9M ------END CERTIFICATE----- - # Issuer: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com # Subject: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com # Label: "XRamp Global CA Root" @@ -575,46 +370,6 @@ VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q= -----END CERTIFICATE----- -# Issuer: O=Government Root Certification Authority -# Subject: O=Government Root Certification Authority -# Label: "Taiwan GRCA" -# Serial: 42023070807708724159991140556527066870 -# MD5 Fingerprint: 37:85:44:53:32:45:1f:20:f0:f3:95:e1:25:c4:43:4e -# SHA1 Fingerprint: f4:8b:11:bf:de:ab:be:94:54:20:71:e6:41:de:6b:be:88:2b:40:b9 -# SHA256 Fingerprint: 76:00:29:5e:ef:e8:5b:9e:1f:d6:24:db:76:06:2a:aa:ae:59:81:8a:54:d2:77:4c:d4:c0:b2:c0:11:31:e1:b3 ------BEGIN CERTIFICATE----- -MIIFcjCCA1qgAwIBAgIQH51ZWtcvwgZEpYAIaeNe9jANBgkqhkiG9w0BAQUFADA/ -MQswCQYDVQQGEwJUVzEwMC4GA1UECgwnR292ZXJubWVudCBSb290IENlcnRpZmlj -YXRpb24gQXV0aG9yaXR5MB4XDTAyMTIwNTEzMjMzM1oXDTMyMTIwNTEzMjMzM1ow -PzELMAkGA1UEBhMCVFcxMDAuBgNVBAoMJ0dvdmVybm1lbnQgUm9vdCBDZXJ0aWZp -Y2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB -AJoluOzMonWoe/fOW1mKydGGEghU7Jzy50b2iPN86aXfTEc2pBsBHH8eV4qNw8XR -IePaJD9IK/ufLqGU5ywck9G/GwGHU5nOp/UKIXZ3/6m3xnOUT0b3EEk3+qhZSV1q -gQdW8or5BtD3cCJNtLdBuTK4sfCxw5w/cP1T3YGq2GN49thTbqGsaoQkclSGxtKy -yhwOeYHWtXBiCAEuTk8O1RGvqa/lmr/czIdtJuTJV6L7lvnM4T9TjGxMfptTCAts -F/tnyMKtsc2AtJfcdgEWFelq16TheEfOhtX7MfP6Mb40qij7cEwdScevLJ1tZqa2 -jWR+tSBqnTuBto9AAGdLiYa4zGX+FVPpBMHWXx1E1wovJ5pGfaENda1UhhXcSTvx -ls4Pm6Dso3pdvtUqdULle96ltqqvKKyskKw4t9VoNSZ63Pc78/1Fm9G7Q3hub/FC -VGqY8A2tl+lSXunVanLeavcbYBT0peS2cWeqH+riTcFCQP5nRhc4L0c/cZyu5SHK -YS1tB6iEfC3uUSXxY5Ce/eFXiGvviiNtsea9P63RPZYLhY3Naye7twWb7LuRqQoH -EgKXTiCQ8P8NHuJBO9NAOueNXdpm5AKwB1KYXA6OM5zCppX7VRluTI6uSw+9wThN -Xo+EHWbNxWCWtFJaBYmOlXqYwZE8lSOyDvR5tMl8wUohAgMBAAGjajBoMB0GA1Ud -DgQWBBTMzO/MKWCkO7GStjz6MmKPrCUVOzAMBgNVHRMEBTADAQH/MDkGBGcqBwAE -MTAvMC0CAQAwCQYFKw4DAhoFADAHBgVnKgMAAAQUA5vwIhP/lSg209yewDL7MTqK -UWUwDQYJKoZIhvcNAQEFBQADggIBAECASvomyc5eMN1PhnR2WPWus4MzeKR6dBcZ -TulStbngCnRiqmjKeKBMmo4sIy7VahIkv9Ro04rQ2JyftB8M3jh+Vzj8jeJPXgyf -qzvS/3WXy6TjZwj/5cAWtUgBfen5Cv8b5Wppv3ghqMKnI6mGq3ZW6A4M9hPdKmaK -ZEk9GhiHkASfQlK3T8v+R0F2Ne//AHY2RTKbxkaFXeIksB7jSJaYV0eUVXoPQbFE -JPPB/hprv4j9wabak2BegUqZIJxIZhm1AHlUD7gsL0u8qV1bYH+Mh6XgUmMqvtg7 -hUAV/h62ZT/FS9p+tXo1KaMuephgIqP0fSdOLeq0dDzpD6QzDxARvBMB1uUO07+1 -EqLhRSPAzAhuYbeJq4PjJB7mXQfnHyA+z2fI56wwbSdLaG5LKlwCCDTb+HbkZ6Mm -nD+iMsJKxYEYMRBWqoTvLQr/uB930r+lWKBi5NdLkXWNiYCYfm3LU05er/ayl4WX -udpVBrkk7tfGOB5jGxI7leFYrPLfhNVfmS8NVVvmONsuP3LpSIXLuykTjx44Vbnz -ssQwmSNOXfJIoRIM3BKQCZBUkQM8R+XVyWXgt0t97EfTsws+rZ7QdAAO671RrcDe -LMDDav7v3Aun+kbfYNucpllQdSNpc5Oy+fwC00fmcc4QAu4njIT/rEUNE1yDMuAl -pYYsfPQS ------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" @@ -706,34 +461,6 @@ vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep +OkuE6N36B9K -----END CERTIFICATE----- -# Issuer: CN=DST Root CA X3 O=Digital Signature Trust Co. -# Subject: CN=DST Root CA X3 O=Digital Signature Trust Co. -# Label: "DST Root CA X3" -# Serial: 91299735575339953335919266965803778155 -# MD5 Fingerprint: 41:03:52:dc:0f:f7:50:1b:16:f0:02:8e:ba:6f:45:c5 -# SHA1 Fingerprint: da:c9:02:4f:54:d8:f6:df:94:93:5f:b1:73:26:38:ca:6a:d7:7c:13 -# SHA256 Fingerprint: 06:87:26:03:31:a7:24:03:d9:09:f1:05:e6:9b:cf:0d:32:e1:bd:24:93:ff:c6:d9:20:6d:11:bc:d6:77:07:39 ------BEGIN CERTIFICATE----- -MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/ -MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT -DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow -PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD -Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB -AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O -rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq -OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b -xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw -7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD -aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV -HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG -SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69 -ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr -AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz -R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5 -JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo -Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ ------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" @@ -816,104 +543,6 @@ hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ubDgEj8Z+7fNzcbBGXJbLy tGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u -----END CERTIFICATE----- -# Issuer: CN=GeoTrust Primary Certification Authority O=GeoTrust Inc. -# Subject: CN=GeoTrust Primary Certification Authority O=GeoTrust Inc. -# Label: "GeoTrust Primary Certification Authority" -# Serial: 32798226551256963324313806436981982369 -# MD5 Fingerprint: 02:26:c3:01:5e:08:30:37:43:a9:d0:7d:cf:37:e6:bf -# SHA1 Fingerprint: 32:3c:11:8e:1b:f7:b8:b6:52:54:e2:e2:10:0d:d6:02:90:37:f0:96 -# SHA256 Fingerprint: 37:d5:10:06:c5:12:ea:ab:62:64:21:f1:ec:8c:92:01:3f:c5:f8:2a:e9:8e:e5:33:eb:46:19:b8:de:b4:d0:6c ------BEGIN CERTIFICATE----- -MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBY -MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMo -R2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEx -MjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgxCzAJBgNVBAYTAlVTMRYwFAYDVQQK -Ew1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQcmltYXJ5IENlcnRp -ZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC -AQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9 -AWbK7hWNb6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjA -ZIVcFU2Ix7e64HXprQU9nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE0 -7e9GceBrAqg1cmuXm2bgyxx5X9gaBGgeRwLmnWDiNpcB3841kt++Z8dtd1k7j53W -kBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGttm/81w7a4DSwDRp35+MI -mO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G -A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJ -KoZIhvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ1 -6CePbJC/kRYkRj5KTs4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl -4b7UVXGYNTq+k+qurUKykG/g/CFNNWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6K -oKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHaFloxt/m0cYASSJlyc1pZU8Fj -UjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG1riR/aYNKxoU -AT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk= ------END CERTIFICATE----- - -# Issuer: CN=thawte Primary Root CA O=thawte, Inc. OU=Certification Services Division/(c) 2006 thawte, Inc. - For authorized use only -# Subject: CN=thawte Primary Root CA O=thawte, Inc. OU=Certification Services Division/(c) 2006 thawte, Inc. - For authorized use only -# Label: "thawte Primary Root CA" -# Serial: 69529181992039203566298953787712940909 -# MD5 Fingerprint: 8c:ca:dc:0b:22:ce:f5:be:72:ac:41:1a:11:a8:d8:12 -# SHA1 Fingerprint: 91:c6:d6:ee:3e:8a:c8:63:84:e5:48:c2:99:29:5c:75:6c:81:7b:81 -# SHA256 Fingerprint: 8d:72:2f:81:a9:c1:13:c0:79:1d:f1:36:a2:96:6d:b2:6c:95:0a:97:1d:b4:6b:41:99:f4:ea:54:b7:8b:fb:9f ------BEGIN CERTIFICATE----- -MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCB -qTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf -Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw -MDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNV -BAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3MDAwMDAwWhcNMzYw -NzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5j -LjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYG -A1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl -IG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqG -SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCsoPD7gFnUnMekz52hWXMJEEUMDSxuaPFs -W0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ1CRfBsDMRJSUjQJib+ta -3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGcq/gcfomk -6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6 -Sk/KaAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94J -NqR32HuHUETVPm4pafs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBA -MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XP -r87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUFAAOCAQEAeRHAS7ORtvzw6WfU -DW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeEuzLlQRHAd9mz -YJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX -xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2 -/qxAeeWsEG89jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/ -LHbTY5xZ3Y+m4Q6gLkH3LpVHz7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7 -jVaMaA== ------END CERTIFICATE----- - -# Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G5 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2006 VeriSign, Inc. - For authorized use only -# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G5 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2006 VeriSign, Inc. - For authorized use only -# Label: "VeriSign Class 3 Public Primary Certification Authority - G5" -# Serial: 33037644167568058970164719475676101450 -# MD5 Fingerprint: cb:17:e4:31:67:3e:e2:09:fe:45:57:93:f3:0a:fa:1c -# SHA1 Fingerprint: 4e:b6:d5:78:49:9b:1c:cf:5f:58:1e:ad:56:be:3d:9b:67:44:a5:e5 -# SHA256 Fingerprint: 9a:cf:ab:7e:43:c8:d8:80:d0:6b:26:2a:94:de:ee:e4:b4:65:99:89:c3:d0:ca:f1:9b:af:64:05:e4:1a:b7:df ------BEGIN CERTIFICATE----- -MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCB -yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL -ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp -U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW -ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0 -aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCByjEL -MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW -ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2ln -biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp -U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y -aXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1 -nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbex -t0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIz -SdhDY2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQG -BO+QueQA5N06tRn/Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+ -rCpSx4/VBEnkjWNHiDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/ -NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E -BAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAH -BgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy -aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKv -MzEzMA0GCSqGSIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzE -p6B4Eq1iDkVwZMXnl2YtmAl+X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y -5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKEKQsTb47bDN0lAtukixlE0kF6BWlK -WE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiCKm0oHw0LxOXnGiYZ -4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vEZV8N -hnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq ------END CERTIFICATE----- - # Issuer: CN=SecureTrust CA O=SecureTrust Corporation # Subject: CN=SecureTrust CA O=SecureTrust Corporation # Label: "SecureTrust CA" @@ -1007,37 +636,6 @@ BA6+C4OmF4O5MBKgxTMVBbkN+8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IB ZQ== -----END CERTIFICATE----- -# Issuer: CN=Network Solutions Certificate Authority O=Network Solutions L.L.C. -# Subject: CN=Network Solutions Certificate Authority O=Network Solutions L.L.C. -# Label: "Network Solutions Certificate Authority" -# Serial: 116697915152937497490437556386812487904 -# MD5 Fingerprint: d3:f3:a6:16:c0:fa:6b:1d:59:b1:2d:96:4d:0e:11:2e -# SHA1 Fingerprint: 74:f8:a3:c3:ef:e7:b3:90:06:4b:83:90:3c:21:64:60:20:e5:df:ce -# SHA256 Fingerprint: 15:f0:ba:00:a3:ac:7a:f3:ac:88:4c:07:2b:10:11:a0:77:bd:77:c0:97:f4:01:64:b2:f8:59:8a:bd:83:86:0c ------BEGIN CERTIFICATE----- -MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBi -MQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu -MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3Jp -dHkwHhcNMDYxMjAxMDAwMDAwWhcNMjkxMjMxMjM1OTU5WjBiMQswCQYDVQQGEwJV -UzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYDVQQDEydO -ZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0GCSqG -SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkvH6SMG3G2I4rC7xGzuAnlt7e+foS0zwz -c7MEL7xxjOWftiJgPl9dzgn/ggwbmlFQGiaJ3dVhXRncEg8tCqJDXRfQNJIg6nPP -OCwGJgl6cvf6UDL4wpPTaaIjzkGxzOTVHzbRijr4jGPiFFlp7Q3Tf2vouAPlT2rl -mGNpSAW+Lv8ztumXWWn4Zxmuk2GWRBXTcrA/vGp97Eh/jcOrqnErU2lBUzS1sLnF -BgrEsEX1QV1uiUV7PTsmjHTC5dLRfbIR1PtYMiKagMnc/Qzpf14Dl847ABSHJ3A4 -qY5usyd2mFHgBeMhqxrVhSI8KbWaFsWAqPS7azCPL0YCorEMIuDTAgMBAAGjgZcw -gZQwHQYDVR0OBBYEFCEwyfsA106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB/wQEAwIB -BjAPBgNVHRMBAf8EBTADAQH/MFIGA1UdHwRLMEkwR6BFoEOGQWh0dHA6Ly9jcmwu -bmV0c29sc3NsLmNvbS9OZXR3b3JrU29sdXRpb25zQ2VydGlmaWNhdGVBdXRob3Jp -dHkuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQC7rkvnt1frf6ott3NHhWrB5KUd5Oc8 -6fRZZXe1eltajSU24HqXLjjAV2CDmAaDn7l2em5Q4LqILPxFzBiwmZVRDuwduIj/ -h1AcgsLj4DKAv6ALR8jDMe+ZZzKATxcheQxpXN5eNK4CtSbqUN9/GGUsyfJj4akH -/nxxH2szJGoeBfcFaMBqEssuXmHLrijTfsK0ZpEmXzwuJF/LWA/rKOyvEZbz3Htv -wKeI8lN3s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHN -pGxlaKFJdlxDydi8NmdspZS11My5vWo1ViHe2MPr+8ukYEywVaCge1ey ------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" @@ -1062,38 +660,6 @@ fQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= -----END CERTIFICATE----- -# Issuer: CN=OISTE WISeKey Global Root GA CA O=WISeKey OU=Copyright (c) 2005/OISTE Foundation Endorsed -# Subject: CN=OISTE WISeKey Global Root GA CA O=WISeKey OU=Copyright (c) 2005/OISTE Foundation Endorsed -# Label: "OISTE WISeKey Global Root GA CA" -# Serial: 86718877871133159090080555911823548314 -# MD5 Fingerprint: bc:6c:51:33:a7:e9:d3:66:63:54:15:72:1b:21:92:93 -# SHA1 Fingerprint: 59:22:a1:e1:5a:ea:16:35:21:f8:98:39:6a:46:46:b0:44:1b:0f:a9 -# SHA256 Fingerprint: 41:c9:23:86:6a:b4:ca:d6:b7:ad:57:80:81:58:2e:02:07:97:a6:cb:df:4f:ff:78:ce:83:96:b3:89:37:d7:f5 ------BEGIN CERTIFICATE----- -MIID8TCCAtmgAwIBAgIQQT1yx/RrH4FDffHSKFTfmjANBgkqhkiG9w0BAQUFADCB -ijELMAkGA1UEBhMCQ0gxEDAOBgNVBAoTB1dJU2VLZXkxGzAZBgNVBAsTEkNvcHly -aWdodCAoYykgMjAwNTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNl -ZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQSBDQTAeFw0w -NTEyMTExNjAzNDRaFw0zNzEyMTExNjA5NTFaMIGKMQswCQYDVQQGEwJDSDEQMA4G -A1UEChMHV0lTZUtleTEbMBkGA1UECxMSQ29weXJpZ2h0IChjKSAyMDA1MSIwIAYD -VQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBX -SVNlS2V5IEdsb2JhbCBSb290IEdBIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A -MIIBCgKCAQEAy0+zAJs9Nt350UlqaxBJH+zYK7LG+DKBKUOVTJoZIyEVRd7jyBxR -VVuuk+g3/ytr6dTqvirdqFEr12bDYVxgAsj1znJ7O7jyTmUIms2kahnBAbtzptf2 -w93NvKSLtZlhuAGio9RN1AU9ka34tAhxZK9w8RxrfvbDd50kc3vkDIzh2TbhmYsF -mQvtRTEJysIA2/dyoJaqlYfQjse2YXMNdmaM3Bu0Y6Kff5MTMPGhJ9vZ/yxViJGg -4E8HsChWjBgbl0SOid3gF27nKu+POQoxhILYQBRJLnpB5Kf+42TMwVlxSywhp1t9 -4B3RLoGbw9ho972WG6xwsRYUC9tguSYBBQIDAQABo1EwTzALBgNVHQ8EBAMCAYYw -DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUswN+rja8sHnR3JQmthG+IbJphpQw -EAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBAEuh/wuHbrP5wUOx -SPMowB0uyQlB+pQAHKSkq0lPjz0e701vvbyk9vImMMkQyh2I+3QZH4VFvbBsUfk2 -ftv1TDI6QU9bR8/oCy22xBmddMVHxjtqD6wU2zz0c5ypBd8A3HR4+vg1YFkCExh8 -vPtNsCBtQ7tgMHpnM1zFmdH4LTlSc/uMqpclXHLZCB6rTjzjgTGfA6b7wP4piFXa -hNVQA7bihKOmNqoROgHhGEvWRGizPflTdISzRpFGlgC3gCy24eMQ4tui5yiPAZZi -Fj4A4xylNoEYokxSdsARo27mHbrjWr42U8U+dY+GaSlYU7Wcu2+fXMUY7N0v4ZjJ -/L7fCg0= ------END CERTIFICATE----- - # Issuer: CN=Certigna O=Dhimyotis # Subject: CN=Certigna O=Dhimyotis # Label: "Certigna" @@ -1124,36 +690,6 @@ t0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== -----END CERTIFICATE----- -# Issuer: CN=Cybertrust Global Root O=Cybertrust, Inc -# Subject: CN=Cybertrust Global Root O=Cybertrust, Inc -# Label: "Cybertrust Global Root" -# Serial: 4835703278459682877484360 -# MD5 Fingerprint: 72:e4:4a:87:e3:69:40:80:77:ea:bc:e3:f4:ff:f0:e1 -# SHA1 Fingerprint: 5f:43:e5:b1:bf:f8:78:8c:ac:1c:c7:ca:4a:9a:c6:22:2b:cc:34:c6 -# SHA256 Fingerprint: 96:0a:df:00:63:e9:63:56:75:0c:29:65:dd:0a:08:67:da:0b:9c:bd:6e:77:71:4a:ea:fb:23:49:ab:39:3d:a3 ------BEGIN CERTIFICATE----- -MIIDoTCCAomgAwIBAgILBAAAAAABD4WqLUgwDQYJKoZIhvcNAQEFBQAwOzEYMBYG -A1UEChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2Jh -bCBSb290MB4XDTA2MTIxNTA4MDAwMFoXDTIxMTIxNTA4MDAwMFowOzEYMBYGA1UE -ChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2JhbCBS -b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+Mi8vRRQZhP/8NN5 -7CPytxrHjoXxEnOmGaoQ25yiZXRadz5RfVb23CO21O1fWLE3TdVJDm71aofW0ozS -J8bi/zafmGWgE07GKmSb1ZASzxQG9Dvj1Ci+6A74q05IlG2OlTEQXO2iLb3VOm2y -HLtgwEZLAfVJrn5GitB0jaEMAs7u/OePuGtm839EAL9mJRQr3RAwHQeWP032a7iP -t3sMpTjr3kfb1V05/Iin89cqdPHoWqI7n1C6poxFNcJQZZXcY4Lv3b93TZxiyWNz -FtApD0mpSPCzqrdsxacwOUBdrsTiXSZT8M4cIwhhqJQZugRiQOwfOHB3EgZxpzAY -XSUnpQIDAQABo4GlMIGiMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/ -MB0GA1UdDgQWBBS2CHsNesysIEyGVjJez6tuhS1wVzA/BgNVHR8EODA2MDSgMqAw -hi5odHRwOi8vd3d3Mi5wdWJsaWMtdHJ1c3QuY29tL2NybC9jdC9jdHJvb3QuY3Js -MB8GA1UdIwQYMBaAFLYIew16zKwgTIZWMl7Pq26FLXBXMA0GCSqGSIb3DQEBBQUA -A4IBAQBW7wojoFROlZfJ+InaRcHUowAl9B8Tq7ejhVhpwjCt2BWKLePJzYFa+HMj -Wqd8BfP9IjsO0QbE2zZMcwSO5bAi5MXzLqXZI+O4Tkogp24CJJ8iYGd7ix1yCcUx -XOl5n4BHPa2hCwcUPUf/A2kaDAtE52Mlp3+yybh2hO0j9n0Hq0V+09+zv+mKts2o -omcrUtW3ZfA5TGOgkXmTUg9U3YO7n9GPp1Nzw8v/MOx8BLjYRB+TX3EJIrduPuoc -A06dGiBh+4E37F78CkWr1+cXVdCg6mCbpvbjjFspwgZgFJ0tl0ypkxWdYcQBX0jW -WL1WMRJOEcgh4LMRkWXbtKaIOM5V ------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" @@ -1223,185 +759,6 @@ i/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7NzTogVZ96edhBiIL5VaZVDADlN 9u6wWk5JRFRYX0KD -----END CERTIFICATE----- -# Issuer: CN=GeoTrust Primary Certification Authority - G3 O=GeoTrust Inc. OU=(c) 2008 GeoTrust Inc. - For authorized use only -# Subject: CN=GeoTrust Primary Certification Authority - G3 O=GeoTrust Inc. OU=(c) 2008 GeoTrust Inc. - For authorized use only -# Label: "GeoTrust Primary Certification Authority - G3" -# Serial: 28809105769928564313984085209975885599 -# MD5 Fingerprint: b5:e8:34:36:c9:10:44:58:48:70:6d:2e:83:d4:b8:05 -# SHA1 Fingerprint: 03:9e:ed:b8:0b:e7:a0:3c:69:53:89:3b:20:d2:d9:32:3a:4c:2a:fd -# SHA256 Fingerprint: b4:78:b8:12:25:0d:f8:78:63:5c:2a:a7:ec:7d:15:5e:aa:62:5e:e8:29:16:e2:cd:29:43:61:88:6c:d1:fb:d4 ------BEGIN CERTIFICATE----- -MIID/jCCAuagAwIBAgIQFaxulBmyeUtB9iepwxgPHzANBgkqhkiG9w0BAQsFADCB -mDELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsT -MChjKSAyMDA4IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25s -eTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhv -cml0eSAtIEczMB4XDTA4MDQwMjAwMDAwMFoXDTM3MTIwMTIzNTk1OVowgZgxCzAJ -BgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykg -MjAwOCBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0 -BgNVBAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg -LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANziXmJYHTNXOTIz -+uvLh4yn1ErdBojqZI4xmKU4kB6Yzy5jK/BGvESyiaHAKAxJcCGVn2TAppMSAmUm -hsalifD614SgcK9PGpc/BkTVyetyEH3kMSj7HGHmKAdEc5IiaacDiGydY8hS2pgn -5whMcD60yRLBxWeDXTPzAxHsatBT4tG6NmCUgLthY2xbF37fQJQeqw3CIShwiP/W -JmxsYAQlTlV+fe+/lEjetx3dcI0FX4ilm/LC7urRQEFtYjgdVgbFA0dRIBn8exAL -DmKudlW/X3e+PkkBUz2YJQN2JFodtNuJ6nnltrM7P7pMKEF/BqxqjsHQ9gUdfeZC -huOl1UcCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw -HQYDVR0OBBYEFMR5yo6hTgMdHNxr2zFblD4/MH8tMA0GCSqGSIb3DQEBCwUAA4IB -AQAtxRPPVoB7eni9n64smefv2t+UXglpp+duaIy9cr5HqQ6XErhK8WTTOd8lNNTB -zU6B8A8ExCSzNJbGpqow32hhc9f5joWJ7w5elShKKiePEI4ufIbEAp7aDHdlDkQN -kv39sxY2+hENHYwOB4lqKVb3cvTdFZx3NWZXqxNT2I7BQMXXExZacse3aQHEerGD -AWh9jUGhlBjBJVz88P6DAod8DQ3PLghcSkANPuyBYeYk28rgDi0Hsj5W3I31QYUH -SJsMC8tJP33st/3LjWeJGqvtux6jAAgIFyqCXDFdRootD4abdNlF+9RAsXqqaC2G -spki4cErx5z481+oghLrGREt ------END CERTIFICATE----- - -# Issuer: CN=thawte Primary Root CA - G2 O=thawte, Inc. OU=(c) 2007 thawte, Inc. - For authorized use only -# Subject: CN=thawte Primary Root CA - G2 O=thawte, Inc. OU=(c) 2007 thawte, Inc. - For authorized use only -# Label: "thawte Primary Root CA - G2" -# Serial: 71758320672825410020661621085256472406 -# MD5 Fingerprint: 74:9d:ea:60:24:c4:fd:22:53:3e:cc:3a:72:d9:29:4f -# SHA1 Fingerprint: aa:db:bc:22:23:8f:c4:01:a1:27:bb:38:dd:f4:1d:db:08:9e:f0:12 -# SHA256 Fingerprint: a4:31:0d:50:af:18:a6:44:71:90:37:2a:86:af:af:8b:95:1f:fb:43:1d:83:7f:1e:56:88:b4:59:71:ed:15:57 ------BEGIN CERTIFICATE----- -MIICiDCCAg2gAwIBAgIQNfwmXNmET8k9Jj1Xm67XVjAKBggqhkjOPQQDAzCBhDEL -MAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjE4MDYGA1UECxMvKGMp -IDIwMDcgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAi -BgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMjAeFw0wNzExMDUwMDAw -MDBaFw0zODAxMTgyMzU5NTlaMIGEMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhh -d3RlLCBJbmMuMTgwNgYDVQQLEy8oYykgMjAwNyB0aGF3dGUsIEluYy4gLSBGb3Ig -YXV0aG9yaXplZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9v -dCBDQSAtIEcyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEotWcgnuVnfFSeIf+iha/ -BebfowJPDQfGAFG6DAJSLSKkQjnE/o/qycG+1E3/n3qe4rF8mq2nhglzh9HnmuN6 -papu+7qzcMBniKI11KOasf2twu8x+qi58/sIxpHR+ymVo0IwQDAPBgNVHRMBAf8E -BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUmtgAMADna3+FGO6Lts6K -DPgR4bswCgYIKoZIzj0EAwMDaQAwZgIxAN344FdHW6fmCsO99YCKlzUNG4k8VIZ3 -KMqh9HneteY4sPBlcIx/AlTCv//YoT7ZzwIxAMSNlPzcU9LcnXgWHxUzI1NS41ox -XZ3Krr0TKUQNJ1uo52icEvdYPy5yAlejj6EULg== ------END CERTIFICATE----- - -# Issuer: CN=thawte Primary Root CA - G3 O=thawte, Inc. OU=Certification Services Division/(c) 2008 thawte, Inc. - For authorized use only -# Subject: CN=thawte Primary Root CA - G3 O=thawte, Inc. OU=Certification Services Division/(c) 2008 thawte, Inc. - For authorized use only -# Label: "thawte Primary Root CA - G3" -# Serial: 127614157056681299805556476275995414779 -# MD5 Fingerprint: fb:1b:5d:43:8a:94:cd:44:c6:76:f2:43:4b:47:e7:31 -# SHA1 Fingerprint: f1:8b:53:8d:1b:e9:03:b6:a6:f0:56:43:5b:17:15:89:ca:f3:6b:f2 -# SHA256 Fingerprint: 4b:03:f4:58:07:ad:70:f2:1b:fc:2c:ae:71:c9:fd:e4:60:4c:06:4c:f5:ff:b6:86:ba:e5:db:aa:d7:fd:d3:4c ------BEGIN CERTIFICATE----- -MIIEKjCCAxKgAwIBAgIQYAGXt0an6rS0mtZLL/eQ+zANBgkqhkiG9w0BAQsFADCB -rjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf -Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw -MDggdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAiBgNV -BAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMzAeFw0wODA0MDIwMDAwMDBa -Fw0zNzEyMDEyMzU5NTlaMIGuMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhhd3Rl -LCBJbmMuMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9u -MTgwNgYDVQQLEy8oYykgMjAwOCB0aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXpl -ZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAtIEcz -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsr8nLPvb2FvdeHsbnndm -gcs+vHyu86YnmjSjaDFxODNi5PNxZnmxqWWjpYvVj2AtP0LMqmsywCPLLEHd5N/8 -YZzic7IilRFDGF/Eth9XbAoFWCLINkw6fKXRz4aviKdEAhN0cXMKQlkC+BsUa0Lf -b1+6a4KinVvnSr0eAXLbS3ToO39/fR8EtCab4LRarEc9VbjXsCZSKAExQGbY2SS9 -9irY7CFJXJv2eul/VTV+lmuNk5Mny5K76qxAwJ/C+IDPXfRa3M50hqY+bAtTyr2S -zhkGcuYMXDhpxwTWvGzOW/b3aJzcJRVIiKHpqfiYnODz1TEoYRFsZ5aNOZnLwkUk -OQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNV -HQ4EFgQUrWyqlGCc7eT/+j4KdCtjA/e2Wb8wDQYJKoZIhvcNAQELBQADggEBABpA -2JVlrAmSicY59BDlqQ5mU1143vokkbvnRFHfxhY0Cu9qRFHqKweKA3rD6z8KLFIW -oCtDuSWQP3CpMyVtRRooOyfPqsMpQhvfO0zAMzRbQYi/aytlryjvsvXDqmbOe1bu -t8jLZ8HJnBoYuMTDSQPxYA5QzUbF83d597YV4Djbxy8ooAw/dyZ02SUS2jHaGh7c -KUGRIjxpp7sC8rZcJwOJ9Abqm+RyguOhCcHpABnTPtRwa7pxpqpYrvS76Wy274fM -m7v/OeZWYdMKp8RcTGB7BXcmer/YB1IsYvdwY9k5vG8cwnncdimvzsUsZAReiDZu -MdRAGmI0Nj81Aa6sY6A= ------END CERTIFICATE----- - -# Issuer: CN=GeoTrust Primary Certification Authority - G2 O=GeoTrust Inc. OU=(c) 2007 GeoTrust Inc. - For authorized use only -# Subject: CN=GeoTrust Primary Certification Authority - G2 O=GeoTrust Inc. OU=(c) 2007 GeoTrust Inc. - For authorized use only -# Label: "GeoTrust Primary Certification Authority - G2" -# Serial: 80682863203381065782177908751794619243 -# MD5 Fingerprint: 01:5e:d8:6b:bd:6f:3d:8e:a1:31:f8:12:e0:98:73:6a -# SHA1 Fingerprint: 8d:17:84:d5:37:f3:03:7d:ec:70:fe:57:8b:51:9a:99:e6:10:d7:b0 -# SHA256 Fingerprint: 5e:db:7a:c4:3b:82:a0:6a:87:61:e8:d7:be:49:79:eb:f2:61:1f:7d:d7:9b:f9:1c:1c:6b:56:6a:21:9e:d7:66 ------BEGIN CERTIFICATE----- -MIICrjCCAjWgAwIBAgIQPLL0SAoA4v7rJDteYD7DazAKBggqhkjOPQQDAzCBmDEL -MAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChj -KSAyMDA3IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2 -MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0 -eSAtIEcyMB4XDTA3MTEwNTAwMDAwMFoXDTM4MDExODIzNTk1OVowgZgxCzAJBgNV -BAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykgMjAw -NyBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNV -BAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBH -MjB2MBAGByqGSM49AgEGBSuBBAAiA2IABBWx6P0DFUPlrOuHNxFi79KDNlJ9RVcL -So17VDs6bl8VAsBQps8lL33KSLjHUGMcKiEIfJo22Av+0SbFWDEwKCXzXV2juLal -tJLtbCyf691DiaI8S0iRHVDsJt/WYC69IaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO -BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBVfNVdRVfslsq0DafwBo/q+EVXVMAoG -CCqGSM49BAMDA2cAMGQCMGSWWaboCd6LuvpaiIjwH5HTRqjySkwCY/tsXzjbLkGT -qQ7mndwxHLKgpxgceeHHNgIwOlavmnRs9vuD4DPTCF+hnMJbn0bWtsuRBmOiBucz -rD6ogRLQy7rQkgu2npaqBA+K ------END CERTIFICATE----- - -# Issuer: CN=VeriSign Universal Root Certification Authority O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2008 VeriSign, Inc. - For authorized use only -# Subject: CN=VeriSign Universal Root Certification Authority O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2008 VeriSign, Inc. - For authorized use only -# Label: "VeriSign Universal Root Certification Authority" -# Serial: 85209574734084581917763752644031726877 -# MD5 Fingerprint: 8e:ad:b5:01:aa:4d:81:e4:8c:1d:d1:e1:14:00:95:19 -# SHA1 Fingerprint: 36:79:ca:35:66:87:72:30:4d:30:a5:fb:87:3b:0f:a7:7b:b7:0d:54 -# SHA256 Fingerprint: 23:99:56:11:27:a5:71:25:de:8c:ef:ea:61:0d:df:2f:a0:78:b5:c8:06:7f:4e:82:82:90:bf:b8:60:e8:4b:3c ------BEGIN CERTIFICATE----- -MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCB -vTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL -ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJp -U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MTgwNgYDVQQDEy9W -ZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe -Fw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJVUzEX -MBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0 -IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9y -IGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNh -bCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF -AAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj1mCOkdeQmIN65lgZOIzF -9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGPMiJhgsWH -H26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+H -LL729fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN -/BMReYTtXlT2NJ8IAfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPT -rJ9VAMf2CGqUuV/c4DPxhGD5WycRtPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1Ud -EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0GCCsGAQUFBwEMBGEwX6FdoFsw -WTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2Oa8PPgGrUSBgs -exkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud -DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4 -sAPmLGd75JR3Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+ -seQxIcaBlVZaDrHC1LGmWazxY8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz -4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTxP/jgdFcrGJ2BtMQo2pSXpXDrrB2+ -BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+PwGZsY6rp2aQW9IHR -lRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4mJO3 -7M2CYfE45k+XmCpajQ== ------END CERTIFICATE----- - -# Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G4 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2007 VeriSign, Inc. - For authorized use only -# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G4 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2007 VeriSign, Inc. - For authorized use only -# Label: "VeriSign Class 3 Public Primary Certification Authority - G4" -# Serial: 63143484348153506665311985501458640051 -# MD5 Fingerprint: 3a:52:e1:e7:fd:6f:3a:e3:6f:f3:6f:99:1b:f9:22:41 -# SHA1 Fingerprint: 22:d5:d8:df:8f:02:31:d1:8d:f7:9d:b7:cf:8a:2d:64:c9:3f:6c:3a -# SHA256 Fingerprint: 69:dd:d7:ea:90:bb:57:c9:3e:13:5d:c8:5e:a6:fc:d5:48:0b:60:32:39:bd:c4:54:fc:75:8b:2a:26:cf:7f:79 ------BEGIN CERTIFICATE----- -MIIDhDCCAwqgAwIBAgIQL4D+I4wOIg9IZxIokYesszAKBggqhkjOPQQDAzCByjEL -MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW -ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2ln -biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp -U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y -aXR5IC0gRzQwHhcNMDcxMTA1MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCByjELMAkG -A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJp -U2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwg -SW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2ln -biBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5 -IC0gRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASnVnp8Utpkmw4tXNherJI9/gHm -GUo9FANL+mAnINmDiWn6VMaaGF5VKmTeBvaNSjutEDxlPZCIBIngMGGzrl0Bp3ve -fLK+ymVhAIau2o970ImtTR1ZmkGxvEeA3J5iw/mjgbIwga8wDwYDVR0TAQH/BAUw -AwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJ -aW1hZ2UvZ2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYj -aHR0cDovL2xvZ28udmVyaXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFLMW -kf3upm7ktS5Jj4d4gYDs5bG1MAoGCCqGSM49BAMDA2gAMGUCMGYhDBgmYFo4e1ZC -4Kf8NoRRkSAsdk1DPcQdhCPQrNZ8NQbOzWm9kA3bbEhCHQ6qQgIxAJw9SDkjOVga -FRJZap7v1VmyHVIsmXHNxynfGyphe3HR3vPA5Q06Sqotp9iGKt0uEA== ------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" @@ -1637,105 +994,6 @@ naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1ZWrOZyGls QyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw== -----END CERTIFICATE----- -# Issuer: CN=Chambers of Commerce Root - 2008 O=AC Camerfirma S.A. -# Subject: CN=Chambers of Commerce Root - 2008 O=AC Camerfirma S.A. -# Label: "Chambers of Commerce Root - 2008" -# Serial: 11806822484801597146 -# MD5 Fingerprint: 5e:80:9e:84:5a:0e:65:0b:17:02:f3:55:18:2a:3e:d7 -# SHA1 Fingerprint: 78:6a:74:ac:76:ab:14:7f:9c:6a:30:50:ba:9e:a8:7e:fe:9a:ce:3c -# SHA256 Fingerprint: 06:3e:4a:fa:c4:91:df:d3:32:f3:08:9b:85:42:e9:46:17:d8:93:d7:fe:94:4e:10:a7:93:7e:e2:9d:96:93:c0 ------BEGIN CERTIFICATE----- -MIIHTzCCBTegAwIBAgIJAKPaQn6ksa7aMA0GCSqGSIb3DQEBBQUAMIGuMQswCQYD -VQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0 -IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3 -MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xKTAnBgNVBAMTIENoYW1iZXJz -IG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4MB4XDTA4MDgwMTEyMjk1MFoXDTM4MDcz -MTEyMjk1MFowga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNlZSBj -dXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29tL2FkZHJlc3MpMRIw -EAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVyZmlybWEgUy5BLjEp -MCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDgwggIiMA0G -CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCvAMtwNyuAWko6bHiUfaN/Gh/2NdW9 -28sNRHI+JrKQUrpjOyhYb6WzbZSm891kDFX29ufyIiKAXuFixrYp4YFs8r/lfTJq -VKAyGVn+H4vXPWCGhSRv4xGzdz4gljUha7MI2XAuZPeEklPWDrCQiorjh40G072Q -DuKZoRuGDtqaCrsLYVAGUvGef3bsyw/QHg3PmTA9HMRFEFis1tPo1+XqxQEHd9ZR -5gN/ikilTWh1uem8nk4ZcfUyS5xtYBkL+8ydddy/Js2Pk3g5eXNeJQ7KXOt3EgfL -ZEFHcpOrUMPrCXZkNNI5t3YRCQ12RcSprj1qr7V9ZS+UWBDsXHyvfuK2GNnQm05a -Sd+pZgvMPMZ4fKecHePOjlO+Bd5gD2vlGts/4+EhySnB8esHnFIbAURRPHsl18Tl -UlRdJQfKFiC4reRB7noI/plvg6aRArBsNlVq5331lubKgdaX8ZSD6e2wsWsSaR6s -+12pxZjptFtYer49okQ6Y1nUCyXeG0+95QGezdIp1Z8XGQpvvwyQ0wlf2eOKNcx5 -Wk0ZN5K3xMGtr/R5JJqyAQuxr1yW84Ay+1w9mPGgP0revq+ULtlVmhduYJ1jbLhj -ya6BXBg14JC7vjxPNyK5fuvPnnchpj04gftI2jE9K+OJ9dC1vX7gUMQSibMjmhAx -hduub+84Mxh2EQIDAQABo4IBbDCCAWgwEgYDVR0TAQH/BAgwBgEB/wIBDDAdBgNV -HQ4EFgQU+SSsD7K1+HnA+mCIG8TZTQKeFxkwgeMGA1UdIwSB2zCB2IAU+SSsD7K1 -+HnA+mCIG8TZTQKeFxmhgbSkgbEwga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpN -YWRyaWQgKHNlZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29t -L2FkZHJlc3MpMRIwEAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVy -ZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAt -IDIwMDiCCQCj2kJ+pLGu2jAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRV -HSAAMCowKAYIKwYBBQUHAgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20w -DQYJKoZIhvcNAQEFBQADggIBAJASryI1wqM58C7e6bXpeHxIvj99RZJe6dqxGfwW -PJ+0W2aeaufDuV2I6A+tzyMP3iU6XsxPpcG1Lawk0lgH3qLPaYRgM+gQDROpI9CF -5Y57pp49chNyM/WqfcZjHwj0/gF/JM8rLFQJ3uIrbZLGOU8W6jx+ekbURWpGqOt1 -glanq6B8aBMz9p0w8G8nOSQjKpD9kCk18pPfNKXG9/jvjA9iSnyu0/VU+I22mlaH -FoI6M6taIgj3grrqLuBHmrS1RaMFO9ncLkVAO+rcf+g769HsJtg1pDDFOqxXnrN2 -pSB7+R5KBWIBpih1YJeSDW4+TTdDDZIVnBgizVGZoCkaPF+KMjNbMMeJL0eYD6MD -xvbxrN8y8NmBGuScvfaAFPDRLLmF9dijscilIeUcE5fuDr3fKanvNFNb0+RqE4QG -tjICxFKuItLcsiFCGtpA8CnJ7AoMXOLQusxI0zcKzBIKinmwPQN/aUv0NCB9szTq -jktk9T79syNnFQ0EuPAtwQlRPLJsFfClI9eDdOTlLsn+mCdCxqvGnrDQWzilm1De -fhiYtUU79nm06PcaewaD+9CL2rvHvRirCG88gGtAPxkZumWK5r7VXNM21+9AUiRg -OGcEMeyP84LG3rlV8zsxkVrctQgVrXYlCg17LofiDKYGvCYQbTed7N14jHyAxfDZ -d0jQ ------END CERTIFICATE----- - -# Issuer: CN=Global Chambersign Root - 2008 O=AC Camerfirma S.A. -# Subject: CN=Global Chambersign Root - 2008 O=AC Camerfirma S.A. -# Label: "Global Chambersign Root - 2008" -# Serial: 14541511773111788494 -# MD5 Fingerprint: 9e:80:ff:78:01:0c:2e:c1:36:bd:fe:96:90:6e:08:f3 -# SHA1 Fingerprint: 4a:bd:ee:ec:95:0d:35:9c:89:ae:c7:52:a1:2c:5b:29:f6:d6:aa:0c -# SHA256 Fingerprint: 13:63:35:43:93:34:a7:69:80:16:a0:d3:24:de:72:28:4e:07:9d:7b:52:20:bb:8f:bd:74:78:16:ee:be:ba:ca ------BEGIN CERTIFICATE----- -MIIHSTCCBTGgAwIBAgIJAMnN0+nVfSPOMA0GCSqGSIb3DQEBBQUAMIGsMQswCQYD -VQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0 -IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3 -MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAlBgNVBAMTHkdsb2JhbCBD -aGFtYmVyc2lnbiBSb290IC0gMjAwODAeFw0wODA4MDExMjMxNDBaFw0zODA3MzEx -MjMxNDBaMIGsMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3Vy -cmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAG -A1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAl -BgNVBAMTHkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwODCCAiIwDQYJKoZI -hvcNAQEBBQADggIPADCCAgoCggIBAMDfVtPkOpt2RbQT2//BthmLN0EYlVJH6xed -KYiONWwGMi5HYvNJBL99RDaxccy9Wglz1dmFRP+RVyXfXjaOcNFccUMd2drvXNL7 -G706tcuto8xEpw2uIRU/uXpbknXYpBI4iRmKt4DS4jJvVpyR1ogQC7N0ZJJ0YPP2 -zxhPYLIj0Mc7zmFLmY/CDNBAspjcDahOo7kKrmCgrUVSY7pmvWjg+b4aqIG7HkF4 -ddPB/gBVsIdU6CeQNR1MM62X/JcumIS/LMmjv9GYERTtY/jKmIhYF5ntRQOXfjyG -HoiMvvKRhI9lNNgATH23MRdaKXoKGCQwoze1eqkBfSbW+Q6OWfH9GzO1KTsXO0G2 -Id3UwD2ln58fQ1DJu7xsepeY7s2MH/ucUa6LcL0nn3HAa6x9kGbo1106DbDVwo3V -yJ2dwW3Q0L9R5OP4wzg2rtandeavhENdk5IMagfeOx2YItaswTXbo6Al/3K1dh3e -beksZixShNBFks4c5eUzHdwHU1SjqoI7mjcv3N2gZOnm3b2u/GSFHTynyQbehP9r -6GsaPMWis0L7iwk+XwhSx2LE1AVxv8Rk5Pihg+g+EpuoHtQ2TS9x9o0o9oOpE9Jh -wZG7SMA0j0GMS0zbaRL/UJScIINZc+18ofLx/d33SdNDWKBWY8o9PeU1VlnpDsog -zCtLkykPAgMBAAGjggFqMIIBZjASBgNVHRMBAf8ECDAGAQH/AgEMMB0GA1UdDgQW -BBS5CcqcHtvTbDprru1U8VuTBjUuXjCB4QYDVR0jBIHZMIHWgBS5CcqcHtvTbDpr -ru1U8VuTBjUuXqGBsqSBrzCBrDELMAkGA1UEBhMCRVUxQzBBBgNVBAcTOk1hZHJp -ZCAoc2VlIGN1cnJlbnQgYWRkcmVzcyBhdCB3d3cuY2FtZXJmaXJtYS5jb20vYWRk -cmVzcykxEjAQBgNVBAUTCUE4Mjc0MzI4NzEbMBkGA1UEChMSQUMgQ2FtZXJmaXJt -YSBTLkEuMScwJQYDVQQDEx5HbG9iYWwgQ2hhbWJlcnNpZ24gUm9vdCAtIDIwMDiC -CQDJzdPp1X0jzjAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCow -KAYIKwYBBQUHAgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZI -hvcNAQEFBQADggIBAICIf3DekijZBZRG/5BXqfEv3xoNa/p8DhxJJHkn2EaqbylZ -UohwEurdPfWbU1Rv4WCiqAm57OtZfMY18dwY6fFn5a+6ReAJ3spED8IXDneRRXoz -X1+WLGiLwUePmJs9wOzL9dWCkoQ10b42OFZyMVtHLaoXpGNR6woBrX/sdZ7LoR/x -fxKxueRkf2fWIyr0uDldmOghp+G9PUIadJpwr2hsUF1Jz//7Dl3mLEfXgTpZALVz -a2Mg9jFFCDkO9HB+QHBaP9BrQql0PSgvAm11cpUJjUhjxsYjV5KTXjXBjfkK9yyd -Yhz2rXzdpjEetrHHfoUm+qRqtdpjMNHvkzeyZi99Bffnt0uYlDXA2TopwZ2yUDMd -SqlapskD7+3056huirRXhOukP9DuqqqHW2Pok+JrqNS4cnhrG+055F3Lm6qH1U9O -AP7Zap88MQ8oAgF9mOinsKJknnn4SPIVqczmyETrP3iZ8ntxPjzxmKfFGBI/5rso -M0LpRQp8bfKGeS/Fghl9CYl8slR2iK7ewfPM4W7bMdaTrpmg7yVqc5iJWzouE4ge -v8CSlDQb4ye3ix5vQv/n6TebUB0tovkC7stYWDpxvGjjqsGvHCgfotwjZT+B6q6Z -09gwzxMNTxXJhLynSC34MCN32EZLeW32jO06f2ARePTpm67VVMB0gNELQp/B ------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" @@ -2034,78 +1292,6 @@ t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6qtnRGEmyR7jTV7JqR50S+kDFy SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03 -----END CERTIFICATE----- -# Issuer: CN=EC-ACC O=Agencia Catalana de Certificacio (NIF Q-0801176-I) OU=Serveis Publics de Certificacio/Vegeu https://www.catcert.net/verarrel (c)03/Jerarquia Entitats de Certificacio Catalanes -# Subject: CN=EC-ACC O=Agencia Catalana de Certificacio (NIF Q-0801176-I) OU=Serveis Publics de Certificacio/Vegeu https://www.catcert.net/verarrel (c)03/Jerarquia Entitats de Certificacio Catalanes -# Label: "EC-ACC" -# Serial: -23701579247955709139626555126524820479 -# MD5 Fingerprint: eb:f5:9d:29:0d:61:f9:42:1f:7c:c2:ba:6d:e3:15:09 -# SHA1 Fingerprint: 28:90:3a:63:5b:52:80:fa:e6:77:4c:0b:6d:a7:d6:ba:a6:4a:f2:e8 -# SHA256 Fingerprint: 88:49:7f:01:60:2f:31:54:24:6a:e2:8c:4d:5a:ef:10:f1:d8:7e:bb:76:62:6f:4a:e0:b7:f9:5b:a7:96:87:99 ------BEGIN CERTIFICATE----- -MIIFVjCCBD6gAwIBAgIQ7is969Qh3hSoYqwE893EATANBgkqhkiG9w0BAQUFADCB -8zELMAkGA1UEBhMCRVMxOzA5BgNVBAoTMkFnZW5jaWEgQ2F0YWxhbmEgZGUgQ2Vy -dGlmaWNhY2lvIChOSUYgUS0wODAxMTc2LUkpMSgwJgYDVQQLEx9TZXJ2ZWlzIFB1 -YmxpY3MgZGUgQ2VydGlmaWNhY2lvMTUwMwYDVQQLEyxWZWdldSBodHRwczovL3d3 -dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbCAoYykwMzE1MDMGA1UECxMsSmVyYXJxdWlh -IEVudGl0YXRzIGRlIENlcnRpZmljYWNpbyBDYXRhbGFuZXMxDzANBgNVBAMTBkVD -LUFDQzAeFw0wMzAxMDcyMzAwMDBaFw0zMTAxMDcyMjU5NTlaMIHzMQswCQYDVQQG -EwJFUzE7MDkGA1UEChMyQWdlbmNpYSBDYXRhbGFuYSBkZSBDZXJ0aWZpY2FjaW8g -KE5JRiBRLTA4MDExNzYtSSkxKDAmBgNVBAsTH1NlcnZlaXMgUHVibGljcyBkZSBD -ZXJ0aWZpY2FjaW8xNTAzBgNVBAsTLFZlZ2V1IGh0dHBzOi8vd3d3LmNhdGNlcnQu -bmV0L3ZlcmFycmVsIChjKTAzMTUwMwYDVQQLEyxKZXJhcnF1aWEgRW50aXRhdHMg -ZGUgQ2VydGlmaWNhY2lvIENhdGFsYW5lczEPMA0GA1UEAxMGRUMtQUNDMIIBIjAN -BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsyLHT+KXQpWIR4NA9h0X84NzJB5R -85iKw5K4/0CQBXCHYMkAqbWUZRkiFRfCQ2xmRJoNBD45b6VLeqpjt4pEndljkYRm -4CgPukLjbo73FCeTae6RDqNfDrHrZqJyTxIThmV6PttPB/SnCWDaOkKZx7J/sxaV -HMf5NLWUhdWZXqBIoH7nF2W4onW4HvPlQn2v7fOKSGRdghST2MDk/7NQcvJ29rNd -QlB50JQ+awwAvthrDk4q7D7SzIKiGGUzE3eeml0aE9jD2z3Il3rucO2n5nzbcc8t -lGLfbdb1OL4/pYUKGbio2Al1QnDE6u/LDsg0qBIimAy4E5S2S+zw0JDnJwIDAQAB -o4HjMIHgMB0GA1UdEQQWMBSBEmVjX2FjY0BjYXRjZXJ0Lm5ldDAPBgNVHRMBAf8E -BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUoMOLRKo3pUW/l4Ba0fF4 -opvpXY0wfwYDVR0gBHgwdjB0BgsrBgEEAfV4AQMBCjBlMCwGCCsGAQUFBwIBFiBo -dHRwczovL3d3dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbDA1BggrBgEFBQcCAjApGidW -ZWdldSBodHRwczovL3d3dy5jYXRjZXJ0Lm5ldC92ZXJhcnJlbCAwDQYJKoZIhvcN -AQEFBQADggEBAKBIW4IB9k1IuDlVNZyAelOZ1Vr/sXE7zDkJlF7W2u++AVtd0x7Y -/X1PzaBB4DSTv8vihpw3kpBWHNzrKQXlxJ7HNd+KDM3FIUPpqojlNcAZQmNaAl6k -SBg6hW/cnbw/nZzBh7h6YQjpdwt/cKt63dmXLGQehb+8dJahw3oS7AwaboMMPOhy -Rp/7SNVel+axofjk70YllJyJ22k4vuxcDlbHZVHlUIiIv0LVKz3l+bqeLrPK9HOS -Agu+TGbrIP65y7WZf+a2E/rKS03Z7lNGBjvGTq2TWoF+bCpLagVFjPIhpDGQh2xl -nJ2lYJU6Un/10asIbvPuW/mIPX64b24D5EI= ------END CERTIFICATE----- - -# Issuer: CN=Hellenic Academic and Research Institutions RootCA 2011 O=Hellenic Academic and Research Institutions Cert. Authority -# Subject: CN=Hellenic Academic and Research Institutions RootCA 2011 O=Hellenic Academic and Research Institutions Cert. Authority -# Label: "Hellenic Academic and Research Institutions RootCA 2011" -# Serial: 0 -# MD5 Fingerprint: 73:9f:4c:4b:73:5b:79:e9:fa:ba:1c:ef:6e:cb:d5:c9 -# SHA1 Fingerprint: fe:45:65:9b:79:03:5b:98:a1:61:b5:51:2e:ac:da:58:09:48:22:4d -# SHA256 Fingerprint: bc:10:4f:15:a4:8b:e7:09:dc:a5:42:a7:e1:d4:b9:df:6f:05:45:27:e8:02:ea:a9:2d:59:54:44:25:8a:fe:71 ------BEGIN CERTIFICATE----- -MIIEMTCCAxmgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UEBhMCR1Ix -RDBCBgNVBAoTO0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1 -dGlvbnMgQ2VydC4gQXV0aG9yaXR5MUAwPgYDVQQDEzdIZWxsZW5pYyBBY2FkZW1p -YyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIFJvb3RDQSAyMDExMB4XDTExMTIw -NjEzNDk1MloXDTMxMTIwMTEzNDk1MlowgZUxCzAJBgNVBAYTAkdSMUQwQgYDVQQK -EztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENl -cnQuIEF1dGhvcml0eTFAMD4GA1UEAxM3SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl -c2VhcmNoIEluc3RpdHV0aW9ucyBSb290Q0EgMjAxMTCCASIwDQYJKoZIhvcNAQEB -BQADggEPADCCAQoCggEBAKlTAOMupvaO+mDYLZU++CwqVE7NuYRhlFhPjz2L5EPz -dYmNUeTDN9KKiE15HrcS3UN4SoqS5tdI1Q+kOilENbgH9mgdVc04UfCMJDGFr4PJ -fel3r+0ae50X+bOdOFAPplp5kYCvN66m0zH7tSYJnTxa71HFK9+WXesyHgLacEns -bgzImjeN9/E2YEsmLIKe0HjzDQ9jpFEw4fkrJxIH2Oq9GGKYsFk3fb7u8yBRQlqD -75O6aRXxYp2fmTmCobd0LovUxQt7L/DICto9eQqakxylKHJzkUOap9FNhYS5qXSP -FEDH3N6sQWRstBmbAmNtJGSPRLIl6s5ddAxjMlyNh+UCAwEAAaOBiTCBhjAPBgNV -HRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQUppFC/RNhSiOeCKQp -5dgTBCPuQSUwRwYDVR0eBEAwPqA8MAWCAy5ncjAFggMuZXUwBoIELmVkdTAGggQu -b3JnMAWBAy5ncjAFgQMuZXUwBoEELmVkdTAGgQQub3JnMA0GCSqGSIb3DQEBBQUA -A4IBAQAf73lB4XtuP7KMhjdCSk4cNx6NZrokgclPEg8hwAOXhiVtXdMiKahsog2p -6z0GW5k6x8zDmjR/qw7IThzh+uTczQ2+vyT+bOdrwg3IBp5OjWEopmr95fZi6hg8 -TqBTnbI6nOulnJEWtk2C4AwFSKls9cz4y51JtPACpf1wA+2KIaWuE4ZJwzNzvoc7 -dIsXRSZMFpGD/md9zU1jZ/rzAxKWeAaNsWftjj++n08C9bMJL/NMh98qy5V8Acys -Nnq/onN694/BtZqhFLKPM58N7yLcZnuEvUUXBj08yrl3NI/K6s8/MT7jiOOASSXI -l7WdmplNsDz4SgCbZN2fOUvRJ9e4 ------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" @@ -2147,35 +1333,6 @@ LysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9vwGYT7JZVEc+NHt4bVaT LnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg== -----END CERTIFICATE----- -# Issuer: O=Trustis Limited OU=Trustis FPS Root CA -# Subject: O=Trustis Limited OU=Trustis FPS Root CA -# Label: "Trustis FPS Root CA" -# Serial: 36053640375399034304724988975563710553 -# MD5 Fingerprint: 30:c9:e7:1e:6b:e6:14:eb:65:b2:16:69:20:31:67:4d -# SHA1 Fingerprint: 3b:c0:38:0b:33:c3:f6:a6:0c:86:15:22:93:d9:df:f5:4b:81:c0:04 -# SHA256 Fingerprint: c1:b4:82:99:ab:a5:20:8f:e9:63:0a:ce:55:ca:68:a0:3e:da:5a:51:9c:88:02:a0:d3:a6:73:be:8f:8e:55:7d ------BEGIN CERTIFICATE----- -MIIDZzCCAk+gAwIBAgIQGx+ttiD5JNM2a/fH8YygWTANBgkqhkiG9w0BAQUFADBF -MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPVHJ1c3RpcyBMaW1pdGVkMRwwGgYDVQQL -ExNUcnVzdGlzIEZQUyBSb290IENBMB4XDTAzMTIyMzEyMTQwNloXDTI0MDEyMTEx -MzY1NFowRTELMAkGA1UEBhMCR0IxGDAWBgNVBAoTD1RydXN0aXMgTGltaXRlZDEc -MBoGA1UECxMTVHJ1c3RpcyBGUFMgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQAD -ggEPADCCAQoCggEBAMVQe547NdDfxIzNjpvto8A2mfRC6qc+gIMPpqdZh8mQRUN+ -AOqGeSoDvT03mYlmt+WKVoaTnGhLaASMk5MCPjDSNzoiYYkchU59j9WvezX2fihH -iTHcDnlkH5nSW7r+f2C/revnPDgpai/lkQtV/+xvWNUtyd5MZnGPDNcE2gfmHhjj -vSkCqPoc4Vu5g6hBSLwacY3nYuUtsuvffM/bq1rKMfFMIvMFE/eC+XN5DL7XSxzA -0RU8k0Fk0ea+IxciAIleH2ulrG6nS4zto3Lmr2NNL4XSFDWaLk6M6jKYKIahkQlB -OrTh4/L68MkKokHdqeMDx4gVOxzUGpTXn2RZEm0CAwEAAaNTMFEwDwYDVR0TAQH/ -BAUwAwEB/zAfBgNVHSMEGDAWgBS6+nEleYtXQSUhhgtx67JkDoshZzAdBgNVHQ4E -FgQUuvpxJXmLV0ElIYYLceuyZA6LIWcwDQYJKoZIhvcNAQEFBQADggEBAH5Y//01 -GX2cGE+esCu8jowU/yyg2kdbw++BLa8F6nRIW/M+TgfHbcWzk88iNVy2P3UnXwmW -zaD+vkAMXBJV+JOCyinpXj9WV4s4NvdFGkwozZ5BuO1WTISkQMi4sKUraXAEasP4 -1BIy+Q7DsdwyhEQsb8tGD+pmQQ9P8Vilpg0ND2HepZ5dfWWhPBfnqFVO76DH7cZE -f1T1o+CP8HxVIo8ptoGj4W1OLBuAZ+ytIJ8MYmHVl/9D7S3B2l0pKoU/rGXuhg8F -jZBf3+6f9L/uHfuY5H+QK4R4EA5sSVPvFVtlRkpdr7r7OnIdzfYliB6XzCGcKQEN -ZetX2fNXlrtIzYE= ------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" @@ -2285,38 +1442,6 @@ e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4p TpPDpFQUWw== -----END CERTIFICATE----- -# Issuer: CN=EE Certification Centre Root CA O=AS Sertifitseerimiskeskus -# Subject: CN=EE Certification Centre Root CA O=AS Sertifitseerimiskeskus -# Label: "EE Certification Centre Root CA" -# Serial: 112324828676200291871926431888494945866 -# MD5 Fingerprint: 43:5e:88:d4:7d:1a:4a:7e:fd:84:2e:52:eb:01:d4:6f -# SHA1 Fingerprint: c9:a8:b9:e7:55:80:5e:58:e3:53:77:a7:25:eb:af:c3:7b:27:cc:d7 -# SHA256 Fingerprint: 3e:84:ba:43:42:90:85:16:e7:75:73:c0:99:2f:09:79:ca:08:4e:46:85:68:1f:f1:95:cc:ba:8a:22:9b:8a:76 ------BEGIN CERTIFICATE----- -MIIEAzCCAuugAwIBAgIQVID5oHPtPwBMyonY43HmSjANBgkqhkiG9w0BAQUFADB1 -MQswCQYDVQQGEwJFRTEiMCAGA1UECgwZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1 -czEoMCYGA1UEAwwfRUUgQ2VydGlmaWNhdGlvbiBDZW50cmUgUm9vdCBDQTEYMBYG -CSqGSIb3DQEJARYJcGtpQHNrLmVlMCIYDzIwMTAxMDMwMTAxMDMwWhgPMjAzMDEy -MTcyMzU5NTlaMHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlBUyBTZXJ0aWZpdHNl -ZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRyZSBS -b290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWUwggEiMA0GCSqGSIb3DQEB -AQUAA4IBDwAwggEKAoIBAQDIIMDs4MVLqwd4lfNE7vsLDP90jmG7sWLqI9iroWUy -euuOF0+W2Ap7kaJjbMeMTC55v6kF/GlclY1i+blw7cNRfdCT5mzrMEvhvH2/UpvO -bntl8jixwKIy72KyaOBhU8E2lf/slLo2rpwcpzIP5Xy0xm90/XsY6KxX7QYgSzIw -WFv9zajmofxwvI6Sc9uXp3whrj3B9UiHbCe9nyV0gVWw93X2PaRka9ZP585ArQ/d -MtO8ihJTmMmJ+xAdTX7Nfh9WDSFwhfYggx/2uh8Ej+p3iDXE/+pOoYtNP2MbRMNE -1CV2yreN1x5KZmTNXMWcg+HCCIia7E6j8T4cLNlsHaFLAgMBAAGjgYowgYcwDwYD -VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBLyWj7qVhy/ -zQas8fElyalL1BSZMEUGA1UdJQQ+MDwGCCsGAQUFBwMCBggrBgEFBQcDAQYIKwYB -BQUHAwMGCCsGAQUFBwMEBggrBgEFBQcDCAYIKwYBBQUHAwkwDQYJKoZIhvcNAQEF -BQADggEBAHv25MANqhlHt01Xo/6tu7Fq1Q+e2+RjxY6hUFaTlrg4wCQiZrxTFGGV -v9DHKpY5P30osxBAIWrEr7BSdxjhlthWXePdNl4dp1BUoMUq5KqMlIpPnTX/dqQG -E5Gion0ARD9V04I8GtVbvFZMIi5GQ4okQC3zErg7cBqklrkar4dBGmoYDQZPxz5u -uSlNDUmJEYcyW+ZLBMjkXOZ0c5RdFpgTlf7727FE5TpwrDdr5rMzcijJs1eg9gIW -iAYLtqZLICjU3j2LrTcFU3T+bsy8QxdxXvnFzBqpYe73dgzzcvRyrc9yAjYHR8/v -GVCJYMzpJJUPwssd8m92kMfMdcGWxZ0= ------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" @@ -3026,27 +2151,6 @@ zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg= -----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: 14367148294922964480859022125800977897474 -# MD5 Fingerprint: 20:f0:27:68:d1:7e:a0:9d:0e:e6:2a:ca:df:5c:89:8e -# SHA1 Fingerprint: 69:69:56:2e:40:80:f4:24:a1:e7:19:9f:14:ba:f3:ee:58:ab:6a:bb -# SHA256 Fingerprint: be:c9:49:11:c2:95:56:76:db:6c:0a:55:09:86:d7:6e:3b:a0:05:66:7c:44:2c:97:62:b4:fb:b7:73:de:22:8c ------BEGIN CERTIFICATE----- -MIIB4TCCAYegAwIBAgIRKjikHJYKBN5CsiilC+g0mAIwCgYIKoZIzj0EAwIwUDEk -MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpH -bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX -DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD -QSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu -MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuMZ5049sJQ6fLjkZHAOkrprlOQcJ -FspjsbmG+IpXwVfOQvpzofdlQv8ewQCybnMO/8ch5RikqtlxP6jUuc6MHaNCMEAw -DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFFSwe61F -uOJAf/sKbvu+M8k8o4TVMAoGCCqGSM49BAMCA0gAMEUCIQDckqGgE6bPA7DmxCGX -kPoUVy0D7O48027KqGx2vKLeuwIgJ6iFJzWbVsaj8kfSt24bAgAXqmemFZHe+pTs -ewv4n4Q= ------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" @@ -3069,86 +2173,6 @@ KoZIzj0EAwMDaAAwZQIxAOVpEslu28YxuglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg xwy8p2Fp8fc74SrL+SvzZpA3 -----END CERTIFICATE----- -# Issuer: CN=Staat der Nederlanden Root CA - G3 O=Staat der Nederlanden -# Subject: CN=Staat der Nederlanden Root CA - G3 O=Staat der Nederlanden -# Label: "Staat der Nederlanden Root CA - G3" -# Serial: 10003001 -# MD5 Fingerprint: 0b:46:67:07:db:10:2f:19:8c:35:50:60:d1:0b:f4:37 -# SHA1 Fingerprint: d8:eb:6b:41:51:92:59:e0:f3:e7:85:00:c0:3d:b6:88:97:c9:ee:fc -# SHA256 Fingerprint: 3c:4f:b0:b9:5a:b8:b3:00:32:f4:32:b8:6f:53:5f:e1:72:c1:85:d0:fd:39:86:58:37:cf:36:18:7f:a6:f4:28 ------BEGIN CERTIFICATE----- -MIIFdDCCA1ygAwIBAgIEAJiiOTANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO -TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh -dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEczMB4XDTEzMTExNDExMjg0MloX -DTI4MTExMzIzMDAwMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl -ciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv -b3QgQ0EgLSBHMzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL4yolQP -cPssXFnrbMSkUeiFKrPMSjTysF/zDsccPVMeiAho2G89rcKezIJnByeHaHE6n3WW -IkYFsO2tx1ueKt6c/DrGlaf1F2cY5y9JCAxcz+bMNO14+1Cx3Gsy8KL+tjzk7FqX -xz8ecAgwoNzFs21v0IJyEavSgWhZghe3eJJg+szeP4TrjTgzkApyI/o1zCZxMdFy -KJLZWyNtZrVtB0LrpjPOktvA9mxjeM3KTj215VKb8b475lRgsGYeCasH/lSJEULR -9yS6YHgamPfJEf0WwTUaVHXvQ9Plrk7O53vDxk5hUUurmkVLoR9BvUhTFXFkC4az -5S6+zqQbwSmEorXLCCN2QyIkHxcE1G6cxvx/K2Ya7Irl1s9N9WMJtxU51nus6+N8 -6U78dULI7ViVDAZCopz35HCz33JvWjdAidiFpNfxC95DGdRKWCyMijmev4SH8RY7 -Ngzp07TKbBlBUgmhHbBqv4LvcFEhMtwFdozL92TkA1CvjJFnq8Xy7ljY3r735zHP -bMk7ccHViLVlvMDoFxcHErVc0qsgk7TmgoNwNsXNo42ti+yjwUOH5kPiNL6VizXt -BznaqB16nzaeErAMZRKQFWDZJkBE41ZgpRDUajz9QdwOWke275dhdU/Z/seyHdTt -XUmzqWrLZoQT1Vyg3N9udwbRcXXIV2+vD3dbAgMBAAGjQjBAMA8GA1UdEwEB/wQF -MAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRUrfrHkleuyjWcLhL75Lpd -INyUVzANBgkqhkiG9w0BAQsFAAOCAgEAMJmdBTLIXg47mAE6iqTnB/d6+Oea31BD -U5cqPco8R5gu4RV78ZLzYdqQJRZlwJ9UXQ4DO1t3ApyEtg2YXzTdO2PCwyiBwpwp -LiniyMMB8jPqKqrMCQj3ZWfGzd/TtiunvczRDnBfuCPRy5FOCvTIeuXZYzbB1N/8 -Ipf3YF3qKS9Ysr1YvY2WTxB1v0h7PVGHoTx0IsL8B3+A3MSs/mrBcDCw6Y5p4ixp -gZQJut3+TcCDjJRYwEYgr5wfAvg1VUkvRtTA8KCWAg8zxXHzniN9lLf9OtMJgwYh -/WA9rjLA0u6NpvDntIJ8CsxwyXmA+P5M9zWEGYox+wrZ13+b8KKaa8MFSu1BYBQw -0aoRQm7TIwIEC8Zl3d1Sd9qBa7Ko+gE4uZbqKmxnl4mUnrzhVNXkanjvSr0rmj1A -fsbAddJu+2gw7OyLnflJNZoaLNmzlTnVHpL3prllL+U9bTpITAjc5CgSKL59NVzq -4BZ+Extq1z7XnvwtdbLBFNUjA9tbbws+eC8N3jONFrdI54OagQ97wUNNVQQXOEpR -1VmiiXTTn74eS9fGbbeIJG9gkaSChVtWQbzQRKtqE77RLFi3EjNYsjdj3BP1lB0/ -QFH1T/U67cjF68IeHRaVesd+QnGTbksVtzDfqu1XhUisHWrdOWnk4Xl4vs4Fv6EM -94B7IWcnMFk= ------END CERTIFICATE----- - -# Issuer: CN=Staat der Nederlanden EV Root CA O=Staat der Nederlanden -# Subject: CN=Staat der Nederlanden EV Root CA O=Staat der Nederlanden -# Label: "Staat der Nederlanden EV Root CA" -# Serial: 10000013 -# MD5 Fingerprint: fc:06:af:7b:e8:1a:f1:9a:b4:e8:d2:70:1f:c0:f5:ba -# SHA1 Fingerprint: 76:e2:7e:c1:4f:db:82:c1:c0:a6:75:b5:05:be:3d:29:b4:ed:db:bb -# SHA256 Fingerprint: 4d:24:91:41:4c:fe:95:67:46:ec:4c:ef:a6:cf:6f:72:e2:8a:13:29:43:2f:9d:8a:90:7a:c4:cb:5d:ad:c1:5a ------BEGIN CERTIFICATE----- -MIIFcDCCA1igAwIBAgIEAJiWjTANBgkqhkiG9w0BAQsFADBYMQswCQYDVQQGEwJO -TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSkwJwYDVQQDDCBTdGFh -dCBkZXIgTmVkZXJsYW5kZW4gRVYgUm9vdCBDQTAeFw0xMDEyMDgxMTE5MjlaFw0y -MjEyMDgxMTEwMjhaMFgxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIg -TmVkZXJsYW5kZW4xKTAnBgNVBAMMIFN0YWF0IGRlciBOZWRlcmxhbmRlbiBFViBS -b290IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA48d+ifkkSzrS -M4M1LGns3Amk41GoJSt5uAg94JG6hIXGhaTK5skuU6TJJB79VWZxXSzFYGgEt9nC -UiY4iKTWO0Cmws0/zZiTs1QUWJZV1VD+hq2kY39ch/aO5ieSZxeSAgMs3NZmdO3d -Z//BYY1jTw+bbRcwJu+r0h8QoPnFfxZpgQNH7R5ojXKhTbImxrpsX23Wr9GxE46p -rfNeaXUmGD5BKyF/7otdBwadQ8QpCiv8Kj6GyzyDOvnJDdrFmeK8eEEzduG/L13l -pJhQDBXd4Pqcfzho0LKmeqfRMb1+ilgnQ7O6M5HTp5gVXJrm0w912fxBmJc+qiXb -j5IusHsMX/FjqTf5m3VpTCgmJdrV8hJwRVXj33NeN/UhbJCONVrJ0yPr08C+eKxC -KFhmpUZtcALXEPlLVPxdhkqHz3/KRawRWrUgUY0viEeXOcDPusBCAUCZSCELa6fS -/ZbV0b5GnUngC6agIk440ME8MLxwjyx1zNDFjFE7PZQIZCZhfbnDZY8UnCHQqv0X -cgOPvZuM5l5Tnrmd74K74bzickFbIZTTRTeU0d8JOV3nI6qaHcptqAqGhYqCvkIH -1vI4gnPah1vlPNOePqc7nvQDs/nxfRN0Av+7oeX6AHkcpmZBiFxgV6YuCcS6/ZrP -px9Aw7vMWgpVSzs4dlG4Y4uElBbmVvMCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB -/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFP6rAJCYniT8qcwaivsnuL8wbqg7 -MA0GCSqGSIb3DQEBCwUAA4ICAQDPdyxuVr5Os7aEAJSrR8kN0nbHhp8dB9O2tLsI -eK9p0gtJ3jPFrK3CiAJ9Brc1AsFgyb/E6JTe1NOpEyVa/m6irn0F3H3zbPB+po3u -2dfOWBfoqSmuc0iH55vKbimhZF8ZE/euBhD/UcabTVUlT5OZEAFTdfETzsemQUHS -v4ilf0X8rLiltTMMgsT7B/Zq5SWEXwbKwYY5EdtYzXc7LMJMD16a4/CrPmEbUCTC -wPTxGfARKbalGAKb12NMcIxHowNDXLldRqANb/9Zjr7dn3LDWyvfjFvO5QxGbJKy -CqNMVEIYFRIYvdr8unRu/8G2oGTYqV9Vrp9canaW2HNnh/tNf1zuacpzEPuKqf2e -vTY4SUmH9A4U8OmHuD+nT3pajnnUk+S7aFKErGzp85hwVXIy+TSrK0m1zSBi5Dp6 -Z2Orltxtrpfs/J92VoguZs9btsmksNcFuuEnL5O7Jiqik7Ab846+HUCjuTaPPoIa -Gl6I6lD4WeKDRikL40Rc4ZW2aZCaFG+XroHPaO+Zmr615+F/+PoTRxZMzG0IQOeL -eG9QgkRQP2YGiqtDhFZKDyAthg710tvSeopLzaXoTvFeJiUBWSOgftL2fiFX1ye8 -FVdMpEbB4IMeDExNH08GGeL5qPQ6gqGyeUN51q1veieQA6TqJIc/2b3Z6fJfUEkc -7uzXLg== ------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" @@ -3756,116 +2780,6 @@ T8p+ck0LcIymSLumoRT2+1hEmRSuqguTaaApJUqlyyvdimYHFngVV3Eb7PVHhPOe MTd61X8kreS8/f3MboPoDKi3QWwH3b08hpcv0g== -----END CERTIFICATE----- -# Issuer: CN=TrustCor RootCert CA-1 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority -# Subject: CN=TrustCor RootCert CA-1 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority -# Label: "TrustCor RootCert CA-1" -# Serial: 15752444095811006489 -# MD5 Fingerprint: 6e:85:f1:dc:1a:00:d3:22:d5:b2:b2:ac:6b:37:05:45 -# SHA1 Fingerprint: ff:bd:cd:e7:82:c8:43:5e:3c:6f:26:86:5c:ca:a8:3a:45:5b:c3:0a -# SHA256 Fingerprint: d4:0e:9c:86:cd:8f:e4:68:c1:77:69:59:f4:9e:a7:74:fa:54:86:84:b6:c4:06:f3:90:92:61:f4:dc:e2:57:5c ------BEGIN CERTIFICATE----- -MIIEMDCCAxigAwIBAgIJANqb7HHzA7AZMA0GCSqGSIb3DQEBCwUAMIGkMQswCQYD -VQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEk -MCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5U -cnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxHzAdBgNVBAMMFlRydXN0Q29y -IFJvb3RDZXJ0IENBLTEwHhcNMTYwMjA0MTIzMjE2WhcNMjkxMjMxMTcyMzE2WjCB -pDELMAkGA1UEBhMCUEExDzANBgNVBAgMBlBhbmFtYTEUMBIGA1UEBwwLUGFuYW1h -IENpdHkxJDAiBgNVBAoMG1RydXN0Q29yIFN5c3RlbXMgUy4gZGUgUi5MLjEnMCUG -A1UECwweVHJ1c3RDb3IgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MR8wHQYDVQQDDBZU -cnVzdENvciBSb290Q2VydCBDQS0xMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB -CgKCAQEAv463leLCJhJrMxnHQFgKq1mqjQCj/IDHUHuO1CAmujIS2CNUSSUQIpid -RtLByZ5OGy4sDjjzGiVoHKZaBeYei0i/mJZ0PmnK6bV4pQa81QBeCQryJ3pS/C3V -seq0iWEk8xoT26nPUu0MJLq5nux+AHT6k61sKZKuUbS701e/s/OojZz0JEsq1pme -9J7+wH5COucLlVPat2gOkEz7cD+PSiyU8ybdY2mplNgQTsVHCJCZGxdNuWxu72CV -EY4hgLW9oHPY0LJ3xEXqWib7ZnZ2+AYfYW0PVcWDtxBWcgYHpfOxGgMFZA6dWorW -hnAbJN7+KIor0Gqw/Hqi3LJ5DotlDwIDAQABo2MwYTAdBgNVHQ4EFgQU7mtJPHo/ -DeOxCbeKyKsZn3MzUOcwHwYDVR0jBBgwFoAU7mtJPHo/DeOxCbeKyKsZn3MzUOcw -DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD -ggEBACUY1JGPE+6PHh0RU9otRCkZoB5rMZ5NDp6tPVxBb5UrJKF5mDo4Nvu7Zp5I -/5CQ7z3UuJu0h3U/IJvOcs+hVcFNZKIZBqEHMwwLKeXx6quj7LUKdJDHfXLy11yf -ke+Ri7fc7Waiz45mO7yfOgLgJ90WmMCV1Aqk5IGadZQ1nJBfiDcGrVmVCrDRZ9MZ -yonnMlo2HD6CqFqTvsbQZJG2z9m2GM/bftJlo6bEjhcxwft+dtvTheNYsnd6djts -L1Ac59v2Z3kf9YKVmgenFK+P3CghZwnS1k1aHBkcjndcw5QkPTJrS37UeJSDvjdN -zl/HHk484IkzlQsPpTLWPFp5LBk= ------END CERTIFICATE----- - -# Issuer: CN=TrustCor RootCert CA-2 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority -# Subject: CN=TrustCor RootCert CA-2 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority -# Label: "TrustCor RootCert CA-2" -# Serial: 2711694510199101698 -# MD5 Fingerprint: a2:e1:f8:18:0b:ba:45:d5:c7:41:2a:bb:37:52:45:64 -# SHA1 Fingerprint: b8:be:6d:cb:56:f1:55:b9:63:d4:12:ca:4e:06:34:c7:94:b2:1c:c0 -# SHA256 Fingerprint: 07:53:e9:40:37:8c:1b:d5:e3:83:6e:39:5d:ae:a5:cb:83:9e:50:46:f1:bd:0e:ae:19:51:cf:10:fe:c7:c9:65 ------BEGIN CERTIFICATE----- -MIIGLzCCBBegAwIBAgIIJaHfyjPLWQIwDQYJKoZIhvcNAQELBQAwgaQxCzAJBgNV -BAYTAlBBMQ8wDQYDVQQIDAZQYW5hbWExFDASBgNVBAcMC1BhbmFtYSBDaXR5MSQw -IgYDVQQKDBtUcnVzdENvciBTeXN0ZW1zIFMuIGRlIFIuTC4xJzAlBgNVBAsMHlRy -dXN0Q29yIENlcnRpZmljYXRlIEF1dGhvcml0eTEfMB0GA1UEAwwWVHJ1c3RDb3Ig -Um9vdENlcnQgQ0EtMjAeFw0xNjAyMDQxMjMyMjNaFw0zNDEyMzExNzI2MzlaMIGk -MQswCQYDVQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEg -Q2l0eTEkMCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYD -VQQLDB5UcnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxHzAdBgNVBAMMFlRy -dXN0Q29yIFJvb3RDZXJ0IENBLTIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK -AoICAQCnIG7CKqJiJJWQdsg4foDSq8GbZQWU9MEKENUCrO2fk8eHyLAnK0IMPQo+ -QVqedd2NyuCb7GgypGmSaIwLgQ5WoD4a3SwlFIIvl9NkRvRUqdw6VC0xK5mC8tkq -1+9xALgxpL56JAfDQiDyitSSBBtlVkxs1Pu2YVpHI7TYabS3OtB0PAx1oYxOdqHp -2yqlO/rOsP9+aij9JxzIsekp8VduZLTQwRVtDr4uDkbIXvRR/u8OYzo7cbrPb1nK -DOObXUm4TOJXsZiKQlecdu/vvdFoqNL0Cbt3Nb4lggjEFixEIFapRBF37120Hape -az6LMvYHL1cEksr1/p3C6eizjkxLAjHZ5DxIgif3GIJ2SDpxsROhOdUuxTTCHWKF -3wP+TfSvPd9cW436cOGlfifHhi5qjxLGhF5DUVCcGZt45vz27Ud+ez1m7xMTiF88 -oWP7+ayHNZ/zgp6kPwqcMWmLmaSISo5uZk3vFsQPeSghYA2FFn3XVDjxklb9tTNM -g9zXEJ9L/cb4Qr26fHMC4P99zVvh1Kxhe1fVSntb1IVYJ12/+CtgrKAmrhQhJ8Z3 -mjOAPF5GP/fDsaOGM8boXg25NSyqRsGFAnWAoOsk+xWq5Gd/bnc/9ASKL3x74xdh -8N0JqSDIvgmk0H5Ew7IwSjiqqewYmgeCK9u4nBit2uBGF6zPXQIDAQABo2MwYTAd -BgNVHQ4EFgQU2f4hQG6UnrybPZx9mCAZ5YwwYrIwHwYDVR0jBBgwFoAU2f4hQG6U -nrybPZx9mCAZ5YwwYrIwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYw -DQYJKoZIhvcNAQELBQADggIBAJ5Fngw7tu/hOsh80QA9z+LqBrWyOrsGS2h60COX -dKcs8AjYeVrXWoSK2BKaG9l9XE1wxaX5q+WjiYndAfrs3fnpkpfbsEZC89NiqpX+ -MWcUaViQCqoL7jcjx1BRtPV+nuN79+TMQjItSQzL/0kMmx40/W5ulop5A7Zv2wnL -/V9lFDfhOPXzYRZY5LVtDQsEGz9QLX+zx3oaFoBg+Iof6Rsqxvm6ARppv9JYx1RX -CI/hOWB3S6xZhBqI8d3LT3jX5+EzLfzuQfogsL7L9ziUwOHQhQ+77Sxzq+3+knYa -ZH9bDTMJBzN7Bj8RpFxwPIXAz+OQqIN3+tvmxYxoZxBnpVIt8MSZj3+/0WvitUfW -2dCFmU2Umw9Lje4AWkcdEQOsQRivh7dvDDqPys/cA8GiCcjl/YBeyGBCARsaU1q7 -N6a3vLqE6R5sGtRk2tRD/pOLS/IseRYQ1JMLiI+h2IYURpFHmygk71dSTlxCnKr3 -Sewn6EAes6aJInKc9Q0ztFijMDvd1GpUk74aTfOTlPf8hAs/hCBcNANExdqtvArB -As8e5ZTZ845b2EzwnexhF7sUMlQMAimTHpKG9n/v55IFDlndmQguLvqcAFLTxWYp -5KeXRKQOKIETNcX2b2TmQcTVL8w0RSXPQQCWPUouwpaYT05KnJe32x+SMsj/D1Fu -1uwJ ------END CERTIFICATE----- - -# Issuer: CN=TrustCor ECA-1 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority -# Subject: CN=TrustCor ECA-1 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority -# Label: "TrustCor ECA-1" -# Serial: 9548242946988625984 -# MD5 Fingerprint: 27:92:23:1d:0a:f5:40:7c:e9:e6:6b:9d:d8:f5:e7:6c -# SHA1 Fingerprint: 58:d1:df:95:95:67:6b:63:c0:f0:5b:1c:17:4d:8b:84:0b:c8:78:bd -# SHA256 Fingerprint: 5a:88:5d:b1:9c:01:d9:12:c5:75:93:88:93:8c:af:bb:df:03:1a:b2:d4:8e:91:ee:15:58:9b:42:97:1d:03:9c ------BEGIN CERTIFICATE----- -MIIEIDCCAwigAwIBAgIJAISCLF8cYtBAMA0GCSqGSIb3DQEBCwUAMIGcMQswCQYD -VQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEk -MCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5U -cnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxFzAVBgNVBAMMDlRydXN0Q29y -IEVDQS0xMB4XDTE2MDIwNDEyMzIzM1oXDTI5MTIzMTE3MjgwN1owgZwxCzAJBgNV -BAYTAlBBMQ8wDQYDVQQIDAZQYW5hbWExFDASBgNVBAcMC1BhbmFtYSBDaXR5MSQw -IgYDVQQKDBtUcnVzdENvciBTeXN0ZW1zIFMuIGRlIFIuTC4xJzAlBgNVBAsMHlRy -dXN0Q29yIENlcnRpZmljYXRlIEF1dGhvcml0eTEXMBUGA1UEAwwOVHJ1c3RDb3Ig -RUNBLTEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDPj+ARtZ+odnbb -3w9U73NjKYKtR8aja+3+XzP4Q1HpGjORMRegdMTUpwHmspI+ap3tDvl0mEDTPwOA -BoJA6LHip1GnHYMma6ve+heRK9jGrB6xnhkB1Zem6g23xFUfJ3zSCNV2HykVh0A5 -3ThFEXXQmqc04L/NyFIduUd+Dbi7xgz2c1cWWn5DkR9VOsZtRASqnKmcp0yJF4Ou -owReUoCLHhIlERnXDH19MURB6tuvsBzvgdAsxZohmz3tQjtQJvLsznFhBmIhVE5/ -wZ0+fyCMgMsq2JdiyIMzkX2woloPV+g7zPIlstR8L+xNxqE6FXrntl019fZISjZF -ZtS6mFjBAgMBAAGjYzBhMB0GA1UdDgQWBBREnkj1zG1I1KBLf/5ZJC+Dl5mahjAf -BgNVHSMEGDAWgBREnkj1zG1I1KBLf/5ZJC+Dl5mahjAPBgNVHRMBAf8EBTADAQH/ -MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEABT41XBVwm8nHc2Fv -civUwo/yQ10CzsSUuZQRg2dd4mdsdXa/uwyqNsatR5Nj3B5+1t4u/ukZMjgDfxT2 -AHMsWbEhBuH7rBiVDKP/mZb3Kyeb1STMHd3BOuCYRLDE5D53sXOpZCz2HAF8P11F -hcCF5yWPldwX8zyfGm6wyuMdKulMY/okYWLW2n62HGz1Ah3UKt1VkOsqEUc8Ll50 -soIipX1TH0XsJ5F95yIW6MBoNtjG8U+ARDL54dHRHareqKucBK+tIA5kmE2la8BI -WJZpTdwHjFGTot+fDz2LYLSCjaoITmJF4PkL0uDgPFveXHEnJcLmA4GLEFPjx1Wi -tJ/X5g== ------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" @@ -4061,126 +2975,6 @@ rYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0EAwMDaAAwZQIwJsdpW9zV Mgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9 -----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: 146587175971765017618439757810265552097 -# MD5 Fingerprint: 82:1a:ef:d4:d2:4a:f2:9f:e2:3d:97:06:14:70:72:85 -# SHA1 Fingerprint: e1:c9:50:e6:ef:22:f8:4c:56:45:72:8b:92:20:60:d7:d5:a7:a3:e8 -# SHA256 Fingerprint: 2a:57:54:71:e3:13:40:bc:21:58:1c:bd:2c:f1:3e:15:84:63:20:3e:ce:94:bc:f9:d3:cc:19:6b:f0:9a:54:72 ------BEGIN CERTIFICATE----- -MIIFWjCCA0KgAwIBAgIQbkepxUtHDA3sM9CJuRz04TANBgkqhkiG9w0BAQwFADBH -MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM -QzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIy -MDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNl -cnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEB -AQUAA4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaM -f/vo27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vX -mX7wCl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7 -zUjwTcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0P -fyblqAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtc -vfaHszVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4 -Zor8Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUsp -zBmkMiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOO -Rc92wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYW -k70paDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+ -DVrNVjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgF -lQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV -HQ4EFgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBADiW -Cu49tJYeX++dnAsznyvgyv3SjgofQXSlfKqE1OXyHuY3UjKcC9FhHb8owbZEKTV1 -d5iyfNm9dKyKaOOpMQkpAWBz40d8U6iQSifvS9efk+eCNs6aaAyC58/UEBZvXw6Z -XPYfcX3v73svfuo21pdwCxXu11xWajOl40k4DLh9+42FpLFZXvRq4d2h9mREruZR -gyFmxhE+885H7pwoHyXa/6xmld01D1zvICxi/ZG6qcz8WpyTgYMpl0p8WnK0OdC3 -d8t5/Wk6kjftbjhlRn7pYL15iJdfOBL07q9bgsiG1eGZbYwE8na6SfZu6W0eX6Dv -J4J2QPim01hcDyxC2kLGe4g0x8HYRZvBPsVhHdljUEn2NIVq4BjFbkerQUIpm/Zg -DdIx02OYI5NaAIFItO/Nis3Jz5nu2Z6qNuFoS3FJFDYoOj0dzpqPJeaAcWErtXvM -+SUWgeExX6GjfhaknBZqlxi9dnKlC54dNuYvoS++cJEPqOba+MSSQGwlfnuzCdyy -F62ARPBopY+Udf90WuioAnwMCeKpSwughQtiue+hMZL77/ZRBIls6Kl0obsXs7X9 -SQ98POyDGCBDTtWTurQ0sR8WNh8M5mQ5Fkzc4P4dyKliPUDqysU0ArSuiYgzNdws -E3PYJ/HQcu51OyLemGhmW/HGY0dVHLqlCFF1pkgl ------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: 146587176055767053814479386953112547951 -# MD5 Fingerprint: 44:ed:9a:0e:a4:09:3b:00:f2:ae:4c:a3:c6:61:b0:8b -# SHA1 Fingerprint: d2:73:96:2a:2a:5e:39:9f:73:3f:e1:c7:1e:64:3f:03:38:34:fc:4d -# SHA256 Fingerprint: c4:5d:7b:b0:8e:6d:67:e6:2e:42:35:11:0b:56:4e:5f:78:fd:92:ef:05:8c:84:0a:ea:4e:64:55:d7:58:5c:60 ------BEGIN CERTIFICATE----- -MIIFWjCCA0KgAwIBAgIQbkepxlqz5yDFMJo/aFLybzANBgkqhkiG9w0BAQwFADBH -MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM -QzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIy -MDAwMDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNl -cnZpY2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEB -AQUAA4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3Lv -CvptnfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3Kg -GjSY6Dlo7JUle3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9Bu -XvAuMC6C/Pq8tBcKSOWIm8Wba96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOd -re7kRXuJVfeKH2JShBKzwkCX44ofR5GmdFrS+LFjKBC4swm4VndAoiaYecb+3yXu -PuWgf9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7MkogwTZq9TwtImoS1 -mKPV+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJGr61K -8YzodDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqj -x5RWIr9qS34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsR -nTKaG73VululycslaVNVJ1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0 -kzCqgc7dGtxRcw1PcOnlthYhGXmy5okLdWTK1au8CcEYof/UVKGFPP0UJAOyh9Ok -twIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV -HQ4EFgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQADggIBALZp -8KZ3/p7uC4Gt4cCpx/k1HUCCq+YEtN/L9x0Pg/B+E02NjO7jMyLDOfxA325BS0JT -vhaI8dI4XsRomRyYUpOM52jtG2pzegVATX9lO9ZY8c6DR2Dj/5epnGB3GFW1fgiT -z9D2PGcDFWEJ+YF59exTpJ/JjwGLc8R3dtyDovUMSRqodt6Sm2T4syzFJ9MHwAiA -pJiS4wGWAqoC7o87xdFtCjMwc3i5T1QWvwsHoaRc5svJXISPD+AVdyx+Jn7axEvb -pxZ3B7DNdehyQtaVhJ2Gg/LkkM0JR9SLA3DaWsYDQvTtN6LwG1BUSw7YhN4ZKJmB -R64JGz9I0cNv4rBgF/XuIwKl2gBbbZCr7qLpGzvpx0QnRY5rn/WkhLx3+WuXrD5R -RaIRpsyF7gpo8j5QOHokYh4XIDdtak23CZvJ/KRY9bb7nE4Yu5UC56GtmwfuNmsk -0jmGwZODUNKBRqhfYlcsu2xkiAhu7xNUX90txGdj08+JN7+dIPT7eoOboB6BAFDC -5AwiWVIQ7UNWhwD4FFKnHYuTjKJNRn8nxnGbJN7k2oaLDX5rIMHAnuFl2GqjpuiF -izoHCBy69Y9Vmhh1fuXsgWbRIXOhNUQLgD1bnF5vKheW0YMjiGZt5obicDIvUiLn -yOd/xCxgXS/Dr55FBcOEArf9LAhST4Ldo/DUhgkC ------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: 146587176140553309517047991083707763997 -# MD5 Fingerprint: 1a:79:5b:6b:04:52:9c:5d:c7:74:33:1b:25:9a:f9:25 -# SHA1 Fingerprint: 30:d4:24:6f:07:ff:db:91:89:8a:0b:e9:49:66:11:eb:8c:5e:46:e5 -# SHA256 Fingerprint: 15:d5:b8:77:46:19:ea:7d:54:ce:1c:a6:d0:b0:c4:03:e0:37:a9:17:f1:31:e8:a0:4e:1e:6b:7a:71:ba:bc:e5 ------BEGIN CERTIFICATE----- -MIICDDCCAZGgAwIBAgIQbkepx2ypcyRAiQ8DVd2NHTAKBggqhkjOPQQDAzBHMQsw -CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU -MBIGA1UEAxMLR1RTIFJvb3QgUjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw -MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp -Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjOPQIBBgUrgQQA -IgNiAAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout -736GjOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2A -DDL24CejQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud -DgQWBBTB8Sa6oC2uhYHP0/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEAgFuk -fCPAlaUs3L6JbyO5o91lAFJekazInXJ0glMLfalAvWhgxeG4VDvBNhcl2MG9AjEA -njWSdIUlUfUk7GRSJFClH9voy8l27OyCbvWFGFPouOOaKaqW04MjyaR7YbPMAuhd ------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: 146587176229350439916519468929765261721 -# MD5 Fingerprint: 5d:b6:6a:c4:60:17:24:6a:1a:99:a8:4b:ee:5e:b4:26 -# SHA1 Fingerprint: 2a:1d:60:27:d9:4a:b1:0a:1c:4d:91:5c:cd:33:a0:cb:3e:2d:54:cb -# SHA256 Fingerprint: 71:cc:a5:39:1f:9e:79:4b:04:80:25:30:b3:63:e1:21:da:8a:30:43:bb:26:66:2f:ea:4d:ca:7f:c9:51:a4:bd ------BEGIN CERTIFICATE----- -MIICCjCCAZGgAwIBAgIQbkepyIuUtui7OyrYorLBmTAKBggqhkjOPQQDAzBHMQsw -CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU -MBIGA1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw -MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp -Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjOPQIBBgUrgQQA -IgNiAATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzu -hXyiQHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/l -xKvRHYqjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud -DgQWBBSATNbrdP9JNqPV2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNnADBkAjBqUFJ0 -CMRw3J5QdCHojXohw0+WbhXRIjVhLfoIN+4Zba3bssx9BzT1YBkstTTZbyACMANx -sbqjYAuG7ZoIapVon+Kz4ZNkfF6Tpt95LY2F45TPI11xzPKwTdb+mciUqXWi4w== ------END CERTIFICATE----- - # Issuer: CN=UCA Global G2 Root O=UniTrust # Subject: CN=UCA Global G2 Root O=UniTrust # Label: "UCA Global G2 Root" @@ -4618,3 +3412,1116 @@ 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----- +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 +-----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----- +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 +-----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=E-Tugra Global Root CA RSA v3 O=E-Tugra EBG A.S. OU=E-Tugra Trust Center +# Subject: CN=E-Tugra Global Root CA RSA v3 O=E-Tugra EBG A.S. OU=E-Tugra Trust Center +# Label: "E-Tugra Global Root CA RSA v3" +# Serial: 75951268308633135324246244059508261641472512052 +# MD5 Fingerprint: 22:be:10:f6:c2:f8:03:88:73:5f:33:29:47:28:47:a4 +# SHA1 Fingerprint: e9:a8:5d:22:14:52:1c:5b:aa:0a:b4:be:24:6a:23:8a:c9:ba:e2:a9 +# SHA256 Fingerprint: ef:66:b0:b1:0a:3c:db:9f:2e:36:48:c7:6b:d2:af:18:ea:d2:bf:e6:f1:17:65:5e:28:c4:06:0d:a1:a3:f4:c2 +-----BEGIN CERTIFICATE----- +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 +-----END CERTIFICATE----- + +# Issuer: CN=E-Tugra Global Root CA ECC v3 O=E-Tugra EBG A.S. OU=E-Tugra Trust Center +# Subject: CN=E-Tugra Global Root CA ECC v3 O=E-Tugra EBG A.S. OU=E-Tugra Trust Center +# Label: "E-Tugra Global Root CA ECC v3" +# Serial: 218504919822255052842371958738296604628416471745 +# MD5 Fingerprint: 46:bc:81:bb:f1:b5:1e:f7:4b:96:bc:14:e2:e7:27:64 +# SHA1 Fingerprint: 8a:2f:af:57:53:b1:b0:e6:a1:04:ec:5b:6a:69:71:6d:f6:1c:e2:84 +# SHA256 Fingerprint: 87:3f:46:85:fa:7f:56:36:25:25:2e:6d:36:bc:d7:f1:6f:c2:49:51:f2:64:e4:7e:1b:95:4f:49:08:cd:ca:13 +-----BEGIN CERTIFICATE----- +MIICpTCCAiqgAwIBAgIUJkYZdzHhT28oNt45UYbm1JeIIsEwCgYIKoZIzj0EAwMw +gYAxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHEwZBbmthcmExGTAXBgNVBAoTEEUtVHVn +cmEgRUJHIEEuUy4xHTAbBgNVBAsTFEUtVHVncmEgVHJ1c3QgQ2VudGVyMSYwJAYD +VQQDEx1FLVR1Z3JhIEdsb2JhbCBSb290IENBIEVDQyB2MzAeFw0yMDAzMTgwOTQ2 +NThaFw00NTAzMTIwOTQ2NThaMIGAMQswCQYDVQQGEwJUUjEPMA0GA1UEBxMGQW5r +YXJhMRkwFwYDVQQKExBFLVR1Z3JhIEVCRyBBLlMuMR0wGwYDVQQLExRFLVR1Z3Jh +IFRydXN0IENlbnRlcjEmMCQGA1UEAxMdRS1UdWdyYSBHbG9iYWwgUm9vdCBDQSBF +Q0MgdjMwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASOmCm/xxAeJ9urA8woLNheSBkQ +KczLWYHMjLiSF4mDKpL2w6QdTGLVn9agRtwcvHbB40fQWxPa56WzZkjnIZpKT4YK +fWzqTTKACrJ6CZtpS5iB4i7sAnCWH/31Rs7K3IKjYzBhMA8GA1UdEwEB/wQFMAMB +Af8wHwYDVR0jBBgwFoAU/4Ixcj75xGZsrTie0bBRiKWQzPUwHQYDVR0OBBYEFP+C +MXI++cRmbK04ntGwUYilkMz1MA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNp +ADBmAjEA5gVYaWHlLcoNy/EZCL3W/VGSGn5jVASQkZo1kTmZ+gepZpO6yGjUij/6 +7W4WAie3AjEA3VoXK3YdZUKWpqxdinlW2Iob35reX8dQj7FbcQwm32pAAOwzkSFx +vmjkI6TZraE3 +-----END CERTIFICATE----- + +# Issuer: CN=Security Communication RootCA3 O=SECOM Trust Systems CO.,LTD. +# Subject: CN=Security Communication RootCA3 O=SECOM Trust Systems CO.,LTD. +# Label: "Security Communication RootCA3" +# Serial: 16247922307909811815 +# MD5 Fingerprint: 1c:9a:16:ff:9e:5c:e0:4d:8a:14:01:f4:35:5d:29:26 +# SHA1 Fingerprint: c3:03:c8:22:74:92:e5:61:a2:9c:5f:79:91:2b:1e:44:13:91:30:3a +# SHA256 Fingerprint: 24:a5:5c:2a:b0:51:44:2d:06:17:76:65:41:23:9a:4a:d0:32:d7:c5:51:75:aa:34:ff:de:2f:bc:4f:5c:52:94 +-----BEGIN CERTIFICATE----- +MIIFfzCCA2egAwIBAgIJAOF8N0D9G/5nMA0GCSqGSIb3DQEBDAUAMF0xCzAJBgNV +BAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScw +JQYDVQQDEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTMwHhcNMTYwNjE2 +MDYxNzE2WhcNMzgwMTE4MDYxNzE2WjBdMQswCQYDVQQGEwJKUDElMCMGA1UEChMc +U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UEAxMeU2VjdXJpdHkg +Q29tbXVuaWNhdGlvbiBSb290Q0EzMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC +CgKCAgEA48lySfcw3gl8qUCBWNO0Ot26YQ+TUG5pPDXC7ltzkBtnTCHsXzW7OT4r +CmDvu20rhvtxosis5FaU+cmvsXLUIKx00rgVrVH+hXShuRD+BYD5UpOzQD11EKzA +lrenfna84xtSGc4RHwsENPXY9Wk8d/Nk9A2qhd7gCVAEF5aEt8iKvE1y/By7z/MG +TfmfZPd+pmaGNXHIEYBMwXFAWB6+oHP2/D5Q4eAvJj1+XCO1eXDe+uDRpdYMQXF7 +9+qMHIjH7Iv10S9VlkZ8WjtYO/u62C21Jdp6Ts9EriGmnpjKIG58u4iFW/vAEGK7 +8vknR+/RiTlDxN/e4UG/VHMgly1s2vPUB6PmudhvrvyMGS7TZ2crldtYXLVqAvO4 +g160a75BflcJdURQVc1aEWEhCmHCqYj9E7wtiS/NYeCVvsq1e+F7NGcLH7YMx3we +GVPKp7FKFSBWFHA9K4IsD50VHUeAR/94mQ4xr28+j+2GaR57GIgUssL8gjMunEst ++3A7caoreyYn8xrC3PsXuKHqy6C0rtOUfnrQq8PsOC0RLoi/1D+tEjtCrI8Cbn3M +0V9hvqG8OmpI6iZVIhZdXw3/JzOfGAN0iltSIEdrRU0id4xVJ/CvHozJgyJUt5rQ +T9nO/NkuHJYosQLTA70lUhw0Zk8jq/R3gpYd0VcwCBEF/VfR2ccCAwEAAaNCMEAw +HQYDVR0OBBYEFGQUfPxYchamCik0FW8qy7z8r6irMA4GA1UdDwEB/wQEAwIBBjAP +BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBDAUAA4ICAQDcAiMI4u8hOscNtybS +YpOnpSNyByCCYN8Y11StaSWSntkUz5m5UoHPrmyKO1o5yGwBQ8IibQLwYs1OY0PA +FNr0Y/Dq9HHuTofjcan0yVflLl8cebsjqodEV+m9NU1Bu0soo5iyG9kLFwfl9+qd +9XbXv8S2gVj/yP9kaWJ5rW4OH3/uHWnlt3Jxs/6lATWUVCvAUm2PVcTJ0rjLyjQI +UYWg9by0F1jqClx6vWPGOi//lkkZhOpn2ASxYfQAW0q3nHE3GYV5v4GwxxMOdnE+ +OoAGrgYWp421wsTL/0ClXI2lyTrtcoHKXJg80jQDdwj98ClZXSEIx2C/pHF7uNke +gr4Jr2VvKKu/S7XuPghHJ6APbw+LP6yVGPO5DtxnVW5inkYO0QR4ynKudtml+LLf +iAlhi+8kTtFZP1rUPcmTPCtk9YENFpb3ksP+MW/oKjJ0DvRMmEoYDjBU1cXrvMUV +nuiZIesnKwkK2/HmcBhWuwzkvvnoEKQTkrgc4NtnHVMDpCKn3F2SEDzq//wbEBrD +2NCcnWXL0CsnMQMeNuE9dnUM/0Umud1RvCPHX9jYhxBAEg09ODfnRDwYwFMJZI// +1ZqmfHAuc1Uh6N//g7kdPjIe1qZ9LPFm6Vwdp6POXiUyK+OVrCoHzrQoeIY8Laad +TdJ0MN1kURXbg4NR16/9M51NZg== +-----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----- diff --git a/shotgun_api3/lib/certifi/core.py b/shotgun_api3/lib/certifi/core.py index 5d2b8cd32..de028981b 100644 --- a/shotgun_api3/lib/certifi/core.py +++ b/shotgun_api3/lib/certifi/core.py @@ -1,20 +1,20 @@ -# -*- coding: utf-8 -*- - """ certifi.py ~~~~~~~~~~ This module returns the installation location of cacert.pem or its contents. """ -import os +import sys -try: - from importlib.resources import path as get_path, read_text + +if sys.version_info >= (3, 11): + + from importlib.resources import as_file, files _CACERT_CTX = None _CACERT_PATH = None - def where(): + def where() -> str: # This is slightly terrible, but we want to delay extracting the file # in cases where we're inside of a zipimport situation until someone # actually calls where(), but we don't want to re-extract the file @@ -33,28 +33,76 @@ def where(): # We also have to hold onto the actual context manager, because # it will do the cleanup whenever it gets garbage collected, so # we will also store that at the global level as well. + _CACERT_CTX = as_file(files("certifi").joinpath("cacert.pem")) + _CACERT_PATH = str(_CACERT_CTX.__enter__()) + + return _CACERT_PATH + + def contents() -> str: + return files("certifi").joinpath("cacert.pem").read_text(encoding="ascii") + +elif sys.version_info >= (3, 7): + + from importlib.resources import path as get_path, read_text + + _CACERT_CTX = None + _CACERT_PATH = None + + def where() -> str: + # This is slightly terrible, but we want to delay extracting the + # file in cases where we're inside of a zipimport situation until + # someone actually calls where(), but we don't want to re-extract + # the file on every call of where(), so we'll do it once then store + # it in a global variable. + global _CACERT_CTX + global _CACERT_PATH + if _CACERT_PATH is None: + # This is slightly janky, the importlib.resources API wants you + # to manage the cleanup of this file, so it doesn't actually + # return a path, it returns a context manager that will give + # you the path when you enter it and will do any cleanup when + # you leave it. In the common case of not needing a temporary + # file, it will just return the file system location and the + # __exit__() is a no-op. + # + # We also have to hold onto the actual context manager, because + # it will do the cleanup whenever it gets garbage collected, so + # we will also store that at the global level as well. _CACERT_CTX = get_path("certifi", "cacert.pem") _CACERT_PATH = str(_CACERT_CTX.__enter__()) return _CACERT_PATH + def contents() -> str: + return read_text("certifi", "cacert.pem", encoding="ascii") + +else: + import os + import types + from typing import Union + + Package = Union[types.ModuleType, str] + Resource = Union[str, "os.PathLike"] -except ImportError: # This fallback will work for Python versions prior to 3.7 that lack the # importlib.resources module but relies on the existing `where` function # so won't address issues with environments like PyOxidizer that don't set # __file__ on modules. - def read_text(_module, _path, encoding="ascii"): - with open(where(), "r", encoding=encoding) as data: + def read_text( + package: Package, + resource: Resource, + encoding: str = 'utf-8', + errors: str = 'strict' + ) -> str: + with open(where(), encoding=encoding) as data: return data.read() # If we don't have importlib.resources, then we will just do the old logic # of assuming we're on the filesystem and munge the path directly. - def where(): + def where() -> str: f = os.path.dirname(__file__) return os.path.join(f, "cacert.pem") - -def contents(): - return read_text("certifi", "cacert.pem", encoding="ascii") + def contents() -> str: + return read_text("certifi", "cacert.pem", encoding="ascii") diff --git a/shotgun_api3/lib/certifi/py.typed b/shotgun_api3/lib/certifi/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/shotgun_api3/lib/requirements.txt b/shotgun_api3/lib/requirements.txt index 79b597634..c4b904c77 100644 --- a/shotgun_api3/lib/requirements.txt +++ b/shotgun_api3/lib/requirements.txt @@ -30,5 +30,5 @@ # released for our dependencies. httplib2==0.19.1 six==1.13.0 -certifi==2020.06.20 +certifi==2022.12.7 pyparsing==2.4.7 \ No newline at end of file From abad4603ff8b9549e9734abf8ba4b5f85102fc42 Mon Sep 17 00:00:00 2001 From: Carlos Villavicencio <123113322+carlos-villavicencio-adsk@users.noreply.github.com> Date: Mon, 24 Apr 2023 14:59:41 -0500 Subject: [PATCH 020/125] SG-30702 Skip SG-MIM entities (#285) * Skip MIM entities * Apply black formatter * Add notes and clean-up --- .gitignore | 1 + azure-pipelines-templates/run-tests.yml | 2 + tests/test_api_long.py | 90 ++++++++++++++++--------- 3 files changed, 61 insertions(+), 32 deletions(-) diff --git a/.gitignore b/.gitignore index 0aaeb9a64..f8adaad52 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ tests/config .cache .travis-solo htmlcov +test-output.xml # setup related build diff --git a/azure-pipelines-templates/run-tests.yml b/azure-pipelines-templates/run-tests.yml index dcf8926b2..aa16bf7ed 100644 --- a/azure-pipelines-templates/run-tests.yml +++ b/azure-pipelines-templates/run-tests.yml @@ -56,6 +56,8 @@ jobs: # Specifies which version of Python we want to use. That's where the strategy comes in. # Each job will share this set of steps, but each of them will receive a different # $(python.version) + # TODO: We should provide `githubToken` if we want to download a python release. + # Otherwise we may hit the GitHub anonymous download limit. - task: UsePythonVersion@0 inputs: versionSpec: '$(python.version)' diff --git a/tests/test_api_long.py b/tests/test_api_long.py index a2d7674c3..65f741cc4 100644 --- a/tests/test_api_long.py +++ b/tests/test_api_long.py @@ -21,7 +21,6 @@ class TestShotgunApiLong(base.LiveTestBase): - def test_automated_find(self): """Called find for each entity type and read all fields""" @@ -35,8 +34,16 @@ def test_automated_find(self): limit = 1 page = 1 for entity_type in all_entities: - if entity_type in ("Asset", "Task", "Shot", "Attachment", - "Candidate"): + if entity_type in ( + "Asset", + "Task", + "Shot", + "Attachment", + "Candidate", + "MimProject", + "MimEntity", + "MimField", + ): continue print("Finding entity type %s" % entity_type) @@ -54,21 +61,27 @@ def test_automated_find(self): # Since no_sorting is not exposed to us, we'll have to rely on # this as a safeguard against trying to sort by a field with # allow_sorting=false. - if field['data_type']["value"] in sortable_types: + if field["data_type"]["value"] in sortable_types: order_field = field_name break # TODO for our test project, we haven't populated these entities.... order = None if order_field: - order = [{'field_name': order_field, 'direction': direction}] + order = [{"field_name": order_field, "direction": direction}] if "project" in fields: - filters = [['project', 'is', self.project]] + filters = [["project", "is", self.project]] else: filters = [] - records = self.sg.find(entity_type, filters, fields=list(fields.keys()), - order=order, filter_operator=filter_operator, - limit=limit, page=page) + records = self.sg.find( + entity_type, + filters, + fields=list(fields.keys()), + order=order, + filter_operator=filter_operator, + limit=limit, + page=page, + ) self.assertTrue(isinstance(records, list)) @@ -117,13 +130,12 @@ def test_schema(self): human_field_name = "Monkey " + str(random.getrandbits(24)) properties = {"description": "How many monkeys were needed"} - new_field_name = self.sg.schema_field_create("Version", "number", human_field_name, - properties=properties) + new_field_name = self.sg.schema_field_create( + "Version", "number", human_field_name, properties=properties + ) properties = {"description": "How many monkeys turned up"} - ret_val = self.sg.schema_field_update("Version", - new_field_name, - properties) + ret_val = self.sg.schema_field_update("Version", new_field_name, properties) self.assertTrue(ret_val) ret_val = self.sg.schema_field_delete("Version", new_field_name) @@ -132,42 +144,56 @@ def test_schema(self): def test_schema_with_project(self): """Called schema functions with project""" - project_entity = {'type': 'Project', 'id': 0} + project_entity = {"type": "Project", "id": 0} if not self.sg.server_caps.version or self.sg.server_caps.version < (5, 4, 4): - # server does not support this! - self.assertRaises(shotgun_api3.ShotgunError, self.sg.schema_entity_read, project_entity) - self.assertRaises(shotgun_api3.ShotgunError, self.sg.schema_read, project_entity) - self.assertRaises(shotgun_api3.ShotgunError, self.sg.schema_field_read, 'Version', None, project_entity) - self.assertRaises(shotgun_api3.ShotgunError, self.sg.schema_field_read, 'Version', 'user', project_entity) + self.assertRaises( + shotgun_api3.ShotgunError, self.sg.schema_entity_read, project_entity + ) + self.assertRaises( + shotgun_api3.ShotgunError, self.sg.schema_read, project_entity + ) + self.assertRaises( + shotgun_api3.ShotgunError, + self.sg.schema_field_read, + "Version", + None, + project_entity, + ) + self.assertRaises( + shotgun_api3.ShotgunError, + self.sg.schema_field_read, + "Version", + "user", + project_entity, + ) else: - schema = self.sg.schema_entity_read(project_entity) self.assertTrue(schema, dict) self.assertTrue(len(schema) > 0) - self.assertTrue('Project' in schema) - self.assertTrue('visible' in schema['Project']) + self.assertTrue("Project" in schema) + self.assertTrue("visible" in schema["Project"]) schema = self.sg.schema_read(project_entity) self.assertTrue(schema, dict) self.assertTrue(len(schema) > 0) - self.assertTrue('Version' in schema) - self.assertFalse('visible' in schema) + self.assertTrue("Version" in schema) + self.assertFalse("visible" in schema) - schema = self.sg.schema_field_read('Version', project_entity=project_entity) + schema = self.sg.schema_field_read("Version", project_entity=project_entity) self.assertTrue(schema, dict) self.assertTrue(len(schema) > 0) - self.assertTrue('user' in schema) - self.assertTrue('visible' in schema['user']) + self.assertTrue("user" in schema) + self.assertTrue("visible" in schema["user"]) - schema = self.sg.schema_field_read('Version', 'user', project_entity) + schema = self.sg.schema_field_read("Version", "user", project_entity) self.assertTrue(schema, dict) self.assertTrue(len(schema) > 0) - self.assertTrue('user' in schema) - self.assertTrue('visible' in schema['user']) + self.assertTrue("user" in schema) + self.assertTrue("visible" in schema["user"]) -if __name__ == '__main__': +if __name__ == "__main__": base.unittest.main() From b3ebbb5664c01b3e98bfd8ee5ffa32da0e3e5c0c Mon Sep 17 00:00:00 2001 From: Carlos Villavicencio <123113322+carlos-villavicencio-adsk@users.noreply.github.com> Date: Tue, 25 Apr 2023 15:54:54 -0500 Subject: [PATCH 021/125] Replace shotgunshoftware references (#284) --- HISTORY.rst | 2 +- shotgun_api3/shotgun.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 6cc547cbb..a03d3f40f 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -182,7 +182,7 @@ v3.0.25 (2016 Jan 12) ===================== - Add handling for Python versions incompatible with SHA-2 (see `this blog post - `_). + `_). - Add ``SHOTGUN_FORCE_CERTIFICATE_VALIDATION`` environment variable to prevent disabling certficate validation when SHA-2 validation is not available. - Add SSL info to user-agent header. diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index ef6d7c5a5..27a2b0e82 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -3558,7 +3558,7 @@ def _make_call(self, verb, path, body, headers): # get raised as well. # # For more info see: - # http://blog.shotgunsoftware.com/2016/01/important-ssl-certificate-renewal-and.html + # https://www.shotgridsoftware.com/blog/important-ssl-certificate-renewal-and-sha-2/ # # SHA-2 errors look like this: # [Errno 1] _ssl.c:480: error:0D0C50A1:asn1 encoding routines:ASN1_item_verify: @@ -3572,8 +3572,8 @@ def _make_call(self, verb, path, body, headers): if self.config.no_ssl_validation is False: LOG.warning("SSL Error: this Python installation is incompatible with " "certificates signed with SHA-2. Disabling certificate validation. " - "For more information, see http://blog.shotgunsoftware.com/2016/01/" - "important-ssl-certificate-renewal-and.html") + "For more information, see https://www.shotgridsoftware.com/blog/" + "important-ssl-certificate-renewal-and-sha-2/") self._turn_off_ssl_validation() # reload user agent to reflect that we have turned off ssl validation req_headers["user-agent"] = "; ".join(self._user_agents) From b3f6c5a2b54a500934d12b51bb6e2abf59484751 Mon Sep 17 00:00:00 2001 From: minna-feng <132297777+minna-feng@users.noreply.github.com> Date: Thu, 17 Aug 2023 11:04:06 -0400 Subject: [PATCH 022/125] SG-31340 Deprecation of Python 2 (#295) * remove references to py2 and skip tests * small fix * revert change * revert change * revert change * revert change * remove badge * use assertRaisesRegex --- README.md | 59 +------------------------ azure-pipelines-templates/run-tests.yml | 2 - tests/base.py | 7 +-- tests/mock.py | 6 --- tests/test_api.py | 6 +-- tests/test_client.py | 1 - tests/test_mockgun.py | 38 ++++------------ tests/test_unit.py | 11 ----- 8 files changed, 11 insertions(+), 119 deletions(-) diff --git a/README.md b/README.md index c587cef42..916fb70df 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![VFX Platform](https://img.shields.io/badge/vfxplatform-2020-blue.svg)](http://www.vfxplatform.com/) -[![Python 2.7 3.7](https://img.shields.io/badge/python-2.7%20%7C%203.7-blue.svg)](https://www.python.org/) +[![Python 3.7](https://img.shields.io/badge/python-3.7-blue.svg)](https://www.python.org/) [![Reference Documentation](http://img.shields.io/badge/doc-reference-blue.svg)](http://developer.shotgridsoftware.com/python-api) [![Build Status](https://dev.azure.com/shotgun-ecosystem/Python%20API/_apis/build/status/shotgunsoftware.python-api?branchName=master)](https://dev.azure.com/shotgun-ecosystem/Python%20API/_build/latest?definitionId=108&branchName=master) [![Coverage Status](https://coveralls.io/repos/github/shotgunsoftware/python-api/badge.svg?branch=master)](https://coveralls.io/github/shotgunsoftware/python-api?branch=master) @@ -37,63 +37,6 @@ The API comes with a copy of the `httplib2` inside the `shotgun_api3/lib` folder where `vX.Y.Z` is a release found on `httplib2`'s [release page](https://github.com/httplib2/httplib2/releases). -## Maintaining Python 2 and 3 compatibility - -python-api should remain compatible with both Python 2, and 3. To make this easier, we use [six](https://six.readthedocs.io/). When adding code that works with types that have changed between Python 2 and 3, notably strings and files, it's advisable to use the `six` types for casting and comparisons. Be sure to follow Python 2 and 3 compatible conventions in code, especially when raising or capturing exceptions and printing. While we don't use `future`, [this page](https://python-future.org/compatible_idioms.html) contains a fairly comprehensive list of Python 2/3 compatibility sticking points to look out for. - -Additionally, the [python-modernize](https://python-modernize.readthedocs.io/en/latest/) tool can be helpful when updating Python 2 code for Python 3 compatibility. - -### Examples: - -#### Comparisons against changed types: - -Python 2: - -``` -if isinstance(my_variable, str): -``` - -Python 2/3: - -``` -if isinstance(my_variable, six.string_types): -``` - -#### Catching exceptions - -Python 2: - -``` -except SomeExceptionType, e: - print "I like to swallow exceptions!" -``` - -Python 2/3: - -``` -from __future__ import print_function -except SomeExceptionType as e: - print("I like to swallow exceptions!") -``` - -#### Print statements - -Python 2: - -``` -print "My spoon is too big!" -``` - -Python 2/3: - -``` -from __future__ import print_function -print("My spoon is too big!") -``` - - -Additionally, when testing locally, tests should be run for both python 2 and python 3 to ensure changes won't break cross-compatibility. - ## Tests Integration and unit tests are provided. diff --git a/azure-pipelines-templates/run-tests.yml b/azure-pipelines-templates/run-tests.yml index aa16bf7ed..31782ef0e 100644 --- a/azure-pipelines-templates/run-tests.yml +++ b/azure-pipelines-templates/run-tests.yml @@ -44,8 +44,6 @@ jobs: strategy: matrix: # We support these two versions of Python. - Python27: - python.version: '2.7' Python37: python.version: '3.7' Python39: diff --git a/tests/base.py b/tests/base.py index c6a2d5926..6793b1e40 100644 --- a/tests/base.py +++ b/tests/base.py @@ -10,12 +10,7 @@ from shotgun_api3.shotgun import ServerCapabilities from shotgun_api3.lib import six from shotgun_api3.lib.six.moves import urllib - -if six.PY2: - from shotgun_api3.lib.six.moves.configparser import SafeConfigParser as ConfigParser -else: - from shotgun_api3.lib.six.moves.configparser import ConfigParser - +from shotgun_api3.lib.six.moves.configparser import ConfigParser try: # Attempt to import skip from unittest. Since this was added in Python 2.7 diff --git a/tests/mock.py b/tests/mock.py index b07172a3c..456c02594 100644 --- a/tests/mock.py +++ b/tests/mock.py @@ -38,12 +38,6 @@ # may not have inspect inspect = None -try: - BaseException -except NameError: - # Python 2.4 compatibility - BaseException = Exception - try: from functools import wraps except ImportError: diff --git a/tests/test_api.py b/tests/test_api.py index 0e341838a..f4bb1eb26 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1206,11 +1206,7 @@ def _id_in_result(self, entity_type, filters, expected_id): for particular filters. """ results = self.sg.find(entity_type, filters) - # can't use 'any' in python 2.4 - for result in results: - if result['id'] == expected_id: - return True - return False + return any(result['id'] == expected_id for result in results) # TODO test all applicable data types for 'in' # 'currency' => [BigDecimal, Float, NilClass], diff --git a/tests/test_client.py b/tests/test_client.py index 540b74710..b1ccdbc33 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -564,7 +564,6 @@ def test_parse_records(self): system = platform.system().lower() if system == 'darwin': local_path_field = "local_path_mac" - # python 2.4 returns 'Microsoft' elif system in ['windows', 'microsoft']: local_path_field = "local_path_windows" elif system == 'linux': diff --git a/tests/test_mockgun.py b/tests/test_mockgun.py index ce8511352..08976d2aa 100644 --- a/tests/test_mockgun.py +++ b/tests/test_mockgun.py @@ -53,28 +53,6 @@ ) -# FIXME: This should probably be refactored into a base class for -# all test bases -class TestBaseWithExceptionTests(unittest.TestCase): - """ - Implements a Python 2.4 compatible assertRaisesRegexp like method. This - was introduced in Python 2.7. - """ - def assertRaisesRegexp(self, exception_type, re_msg, func): - try: - func() - except exception_type as exception: - matches = re.findall(re_msg, str(exception)) - if not matches: - self.fail("Expected exception to match '%s', got '%s' instead." % ( - re_msg, str(exception) - )) - except Exception as ex: - self.fail("Expected exception of type %s, got %s" % (exception_type, type(ex))) - else: - self.fail("Expected %s was not raised." % exception_type) - - class TestMockgunModuleInterface(unittest.TestCase): """ mockgun.py was turned into a module. Ensure we haven't broken the interface. @@ -93,7 +71,7 @@ def test_interface_intact(self): mockgun.Shotgun -class TestValidateFilterSyntax(TestBaseWithExceptionTests): +class TestValidateFilterSyntax(unittest.TestCase): """ Tests filter syntax support. """ @@ -127,7 +105,7 @@ def test_filter_array_or_dict(self): ) # We can't have not dict/list values for filters however. - self.assertRaisesRegexp( + self.assertRaisesRegex( ShotgunError, "Filters can only be lists or dictionaries, not int.", lambda: self._mockgun.find( @@ -137,7 +115,7 @@ def test_filter_array_or_dict(self): ) -class TestEntityFieldComparison(TestBaseWithExceptionTests): +class TestEntityFieldComparison(unittest.TestCase): """ Checks if entity fields comparison work. """ @@ -191,7 +169,7 @@ def test_find_entity_with_none_link(self): self.assertEqual(items[0]["id"], self._project_link["id"]) -class TestTextFieldOperators(TestBaseWithExceptionTests): +class TestTextFieldOperators(unittest.TestCase): """ Checks if text field comparison work. """ @@ -210,7 +188,7 @@ def test_operator_contains(self): self.assertTrue(item) -class TestMultiEntityFieldComparison(TestBaseWithExceptionTests): +class TestMultiEntityFieldComparison(unittest.TestCase): """ Ensures multi entity field comparison work. """ @@ -293,7 +271,7 @@ def test_find_with_none(self): self.assertTrue(len(item["users"]) > 0) -class TestFilterOperator(TestBaseWithExceptionTests): +class TestFilterOperator(unittest.TestCase): """ Unit tests for the filter_operator filter syntax. """ @@ -408,7 +386,7 @@ def test_nested_filter_operators(self): def test_invalid_operator(self): - self.assertRaisesRegexp( + self.assertRaisesRegex( ShotgunError, "Unknown filter_operator type: bad", lambda: self._mockgun.find( @@ -421,7 +399,7 @@ def test_invalid_operator(self): ]) ) - self.assertRaisesRegexp( + self.assertRaisesRegex( ShotgunError, "Bad filter operator, requires keys 'filter_operator' and 'filters',", lambda: self._mockgun.find( diff --git a/tests/test_unit.py b/tests/test_unit.py index 1755b51cb..c8ce4fcc9 100644 --- a/tests/test_unit.py +++ b/tests/test_unit.py @@ -506,16 +506,5 @@ def _test_mimetypes_import(self, platform, major, minor, patch_number, result, m mock.platform = platform self.assertEqual(_is_mimetypes_broken(), result) - def test_correct_mimetypes_imported(self): - """ - Makes sure fix is imported for only for Python 2.7.0 to 2.7.7 on win32 - """ - self._test_mimetypes_import("win32", 2, 6, 9, False) - for patch_number in range(0, 10): # 0 to 9 inclusively - self._test_mimetypes_import("win32", 2, 7, patch_number, True) - self._test_mimetypes_import("win32", 2, 7, 10, False) - self._test_mimetypes_import("win32", 3, 0, 0, False) - self._test_mimetypes_import("darwin", 2, 7, 0, False) - if __name__ == '__main__': unittest.main() From b8a907fea09ae227e381977ffec4a342a2485f14 Mon Sep 17 00:00:00 2001 From: minna-feng <132297777+minna-feng@users.noreply.github.com> Date: Mon, 28 Aug 2023 15:52:57 -0400 Subject: [PATCH 023/125] SG-31853 Security upgrade certifi to latest version 2023.07.22 (#296) * update certifi 2023.07.22 * update requirements.txt * SG-31340 Deprecation of Python 2 (#295) * remove references to py2 and skip tests * small fix * revert change * revert change * revert change * revert change * remove badge * use assertRaisesRegex * Empty commit --------- Co-authored-by: Carlos Villavicencio --- shotgun_api3/lib/certifi/__init__.py | 2 +- shotgun_api3/lib/certifi/cacert.pem | 386 +++++++++++++++++---------- shotgun_api3/lib/requirements.txt | 2 +- 3 files changed, 249 insertions(+), 141 deletions(-) diff --git a/shotgun_api3/lib/certifi/__init__.py b/shotgun_api3/lib/certifi/__init__.py index a3546f125..8ce89cef7 100644 --- a/shotgun_api3/lib/certifi/__init__.py +++ b/shotgun_api3/lib/certifi/__init__.py @@ -1,4 +1,4 @@ from .core import contents, where __all__ = ["contents", "where"] -__version__ = "2022.12.07" +__version__ = "2023.07.22" diff --git a/shotgun_api3/lib/certifi/cacert.pem b/shotgun_api3/lib/certifi/cacert.pem index df9e4e3c7..02123695d 100644 --- a/shotgun_api3/lib/certifi/cacert.pem +++ b/shotgun_api3/lib/certifi/cacert.pem @@ -791,34 +791,6 @@ uLjbvrW5KfnaNwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2 XjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= -----END CERTIFICATE----- -# Issuer: CN=Hongkong Post Root CA 1 O=Hongkong Post -# Subject: CN=Hongkong Post Root CA 1 O=Hongkong Post -# Label: "Hongkong Post Root CA 1" -# Serial: 1000 -# MD5 Fingerprint: a8:0d:6f:39:78:b9:43:6d:77:42:6d:98:5a:cc:23:ca -# SHA1 Fingerprint: d6:da:a8:20:8d:09:d2:15:4d:24:b5:2f:cb:34:6e:b2:58:b2:8a:58 -# SHA256 Fingerprint: f9:e6:7d:33:6c:51:00:2a:c0:54:c6:32:02:2d:66:dd:a2:e7:e3:ff:f1:0a:d0:61:ed:31:d8:bb:b4:10:cf:b2 ------BEGIN CERTIFICATE----- -MIIDMDCCAhigAwIBAgICA+gwDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCSEsx -FjAUBgNVBAoTDUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3Qg -Um9vdCBDQSAxMB4XDTAzMDUxNTA1MTMxNFoXDTIzMDUxNTA0NTIyOVowRzELMAkG -A1UEBhMCSEsxFjAUBgNVBAoTDUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdr -b25nIFBvc3QgUm9vdCBDQSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC -AQEArP84tulmAknjorThkPlAj3n54r15/gK97iSSHSL22oVyaf7XPwnU3ZG1ApzQ -jVrhVcNQhrkpJsLj2aDxaQMoIIBFIi1WpztUlVYiWR8o3x8gPW2iNr4joLFutbEn -PzlTCeqrauh0ssJlXI6/fMN4hM2eFvz1Lk8gKgifd/PFHsSaUmYeSF7jEAaPIpjh -ZY4bXSNmO7ilMlHIhqqhqZ5/dpTCpmy3QfDVyAY45tQM4vM7TG1QjMSDJ8EThFk9 -nnV0ttgCXjqQesBCNnLsak3c78QA3xMYV18meMjWCnl3v/evt3a5pQuEF10Q6m/h -q5URX208o1xNg1vysxmKgIsLhwIDAQABoyYwJDASBgNVHRMBAf8ECDAGAQH/AgED -MA4GA1UdDwEB/wQEAwIBxjANBgkqhkiG9w0BAQUFAAOCAQEADkbVPK7ih9legYsC -mEEIjEy82tvuJxuC52pF7BaLT4Wg87JwvVqWuspube5Gi27nKi6Wsxkz67SfqLI3 -7piol7Yutmcn1KZJ/RyTZXaeQi/cImyaT/JaFTmxcdcrUehtHJjA2Sr0oYJ71clB -oiMBdDhViw+5LmeiIAQ32pwL0xch4I+XeTRvhEgCIDMb5jREn5Fw9IBehEPCKdJs -EhTkYY2sEJCehFC78JZvRZ+K88psT/oROhUVRsPNH4NbLUES7VBnQRM9IauUiqpO -fMGx+6fWtScvl6tu4B3i0RwsH0Ti/L6RoZz71ilTc4afU9hDDl3WY4JxHYB0yvbi -AmvZWg== ------END CERTIFICATE----- - # Issuer: CN=SecureSign RootCA11 O=Japan Certification Services, Inc. # Subject: CN=SecureSign RootCA11 O=Japan Certification Services, Inc. # Label: "SecureSign RootCA11" @@ -1676,50 +1648,6 @@ HL/EVlP6Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVx SK236thZiNSQvxaz2emsWWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY= -----END CERTIFICATE----- -# Issuer: CN=E-Tugra Certification Authority O=E-Tu\u011fra EBG Bili\u015fim Teknolojileri ve Hizmetleri A.\u015e. OU=E-Tugra Sertifikasyon Merkezi -# Subject: CN=E-Tugra Certification Authority O=E-Tu\u011fra EBG Bili\u015fim Teknolojileri ve Hizmetleri A.\u015e. OU=E-Tugra Sertifikasyon Merkezi -# Label: "E-Tugra Certification Authority" -# Serial: 7667447206703254355 -# MD5 Fingerprint: b8:a1:03:63:b0:bd:21:71:70:8a:6f:13:3a:bb:79:49 -# SHA1 Fingerprint: 51:c6:e7:08:49:06:6e:f3:92:d4:5c:a0:0d:6d:a3:62:8f:c3:52:39 -# SHA256 Fingerprint: b0:bf:d5:2b:b0:d7:d9:bd:92:bf:5d:4d:c1:3d:a2:55:c0:2c:54:2f:37:83:65:ea:89:39:11:f5:5e:55:f2:3c ------BEGIN CERTIFICATE----- -MIIGSzCCBDOgAwIBAgIIamg+nFGby1MwDQYJKoZIhvcNAQELBQAwgbIxCzAJBgNV -BAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExQDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBC -aWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhpem1ldGxlcmkgQS7Fni4xJjAkBgNV -BAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBNZXJrZXppMSgwJgYDVQQDDB9FLVR1 -Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTEzMDMwNTEyMDk0OFoXDTIz -MDMwMzEyMDk0OFowgbIxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExQDA+ -BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhp -em1ldGxlcmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBN -ZXJrZXppMSgwJgYDVQQDDB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 -MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4vU/kwVRHoViVF56C/UY -B4Oufq9899SKa6VjQzm5S/fDxmSJPZQuVIBSOTkHS0vdhQd2h8y/L5VMzH2nPbxH -D5hw+IyFHnSOkm0bQNGZDbt1bsipa5rAhDGvykPL6ys06I+XawGb1Q5KCKpbknSF -Q9OArqGIW66z6l7LFpp3RMih9lRozt6Plyu6W0ACDGQXwLWTzeHxE2bODHnv0ZEo -q1+gElIwcxmOj+GMB6LDu0rw6h8VqO4lzKRG+Bsi77MOQ7osJLjFLFzUHPhdZL3D -k14opz8n8Y4e0ypQBaNV2cvnOVPAmJ6MVGKLJrD3fY185MaeZkJVgkfnsliNZvcH -fC425lAcP9tDJMW/hkd5s3kc91r0E+xs+D/iWR+V7kI+ua2oMoVJl0b+SzGPWsut -dEcf6ZG33ygEIqDUD13ieU/qbIWGvaimzuT6w+Gzrt48Ue7LE3wBf4QOXVGUnhMM -ti6lTPk5cDZvlsouDERVxcr6XQKj39ZkjFqzAQqptQpHF//vkUAqjqFGOjGY5RH8 -zLtJVor8udBhmm9lbObDyz51Sf6Pp+KJxWfXnUYTTjF2OySznhFlhqt/7x3U+Lzn -rFpct1pHXFXOVbQicVtbC/DP3KBhZOqp12gKY6fgDT+gr9Oq0n7vUaDmUStVkhUX -U8u3Zg5mTPj5dUyQ5xJwx0UCAwEAAaNjMGEwHQYDVR0OBBYEFC7j27JJ0JxUeVz6 -Jyr+zE7S6E5UMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAULuPbsknQnFR5 -XPonKv7MTtLoTlQwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAF -Nzr0TbdF4kV1JI+2d1LoHNgQk2Xz8lkGpD4eKexd0dCrfOAKkEh47U6YA5n+KGCR -HTAduGN8qOY1tfrTYXbm1gdLymmasoR6d5NFFxWfJNCYExL/u6Au/U5Mh/jOXKqY -GwXgAEZKgoClM4so3O0409/lPun++1ndYYRP0lSWE2ETPo+Aab6TR7U1Q9Jauz1c -77NCR807VRMGsAnb/WP2OogKmW9+4c4bU2pEZiNRCHu8W1Ki/QY3OEBhj0qWuJA3 -+GbHeJAAFS6LrVE1Uweoa2iu+U48BybNCAVwzDk/dr2l02cmAYamU9JgO3xDf1WK -vJUawSg5TB9D0pH0clmKuVb8P7Sd2nCcdlqMQ1DujjByTd//SffGqWfZbawCEeI6 -FiWnWAjLb1NBnEg4R2gz0dfHj9R0IdTDBZB6/86WiLEVKV0jq9BgoRJP3vQXzTLl -yb/IQ639Lo7xr+L0mPoSHyDYwKcMhcWQ9DstliaxLL5Mq+ux0orJ23gTDx4JnW2P -AJ8C2sH6H3p6CcRK5ogql5+Ji/03X186zjhZhkuvcQu02PJwT58yE+Owp1fl2tpD -y4Q08ijE6m30Ku/Ba3ba+367hTzSU8JNvnHhRdH9I2cNE3X7z2VnIp2usAnRCf8d -NL/+I5c30jn6PQ0GC7TbO6Orb1wdtn7os4I07QZcJA== ------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" @@ -4397,73 +4325,6 @@ ut6Dacpps6kFtZaSF4fC0urQe87YQVt8rgIwRt7qy12a7DLCZRawTDBcMPPaTnOG BtjOiQRINzf43TNRnXCve1XYAS59BWQOhriR -----END CERTIFICATE----- -# Issuer: CN=E-Tugra Global Root CA RSA v3 O=E-Tugra EBG A.S. OU=E-Tugra Trust Center -# Subject: CN=E-Tugra Global Root CA RSA v3 O=E-Tugra EBG A.S. OU=E-Tugra Trust Center -# Label: "E-Tugra Global Root CA RSA v3" -# Serial: 75951268308633135324246244059508261641472512052 -# MD5 Fingerprint: 22:be:10:f6:c2:f8:03:88:73:5f:33:29:47:28:47:a4 -# SHA1 Fingerprint: e9:a8:5d:22:14:52:1c:5b:aa:0a:b4:be:24:6a:23:8a:c9:ba:e2:a9 -# SHA256 Fingerprint: ef:66:b0:b1:0a:3c:db:9f:2e:36:48:c7:6b:d2:af:18:ea:d2:bf:e6:f1:17:65:5e:28:c4:06:0d:a1:a3:f4:c2 ------BEGIN CERTIFICATE----- -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 ------END CERTIFICATE----- - -# Issuer: CN=E-Tugra Global Root CA ECC v3 O=E-Tugra EBG A.S. OU=E-Tugra Trust Center -# Subject: CN=E-Tugra Global Root CA ECC v3 O=E-Tugra EBG A.S. OU=E-Tugra Trust Center -# Label: "E-Tugra Global Root CA ECC v3" -# Serial: 218504919822255052842371958738296604628416471745 -# MD5 Fingerprint: 46:bc:81:bb:f1:b5:1e:f7:4b:96:bc:14:e2:e7:27:64 -# SHA1 Fingerprint: 8a:2f:af:57:53:b1:b0:e6:a1:04:ec:5b:6a:69:71:6d:f6:1c:e2:84 -# SHA256 Fingerprint: 87:3f:46:85:fa:7f:56:36:25:25:2e:6d:36:bc:d7:f1:6f:c2:49:51:f2:64:e4:7e:1b:95:4f:49:08:cd:ca:13 ------BEGIN CERTIFICATE----- -MIICpTCCAiqgAwIBAgIUJkYZdzHhT28oNt45UYbm1JeIIsEwCgYIKoZIzj0EAwMw -gYAxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHEwZBbmthcmExGTAXBgNVBAoTEEUtVHVn -cmEgRUJHIEEuUy4xHTAbBgNVBAsTFEUtVHVncmEgVHJ1c3QgQ2VudGVyMSYwJAYD -VQQDEx1FLVR1Z3JhIEdsb2JhbCBSb290IENBIEVDQyB2MzAeFw0yMDAzMTgwOTQ2 -NThaFw00NTAzMTIwOTQ2NThaMIGAMQswCQYDVQQGEwJUUjEPMA0GA1UEBxMGQW5r -YXJhMRkwFwYDVQQKExBFLVR1Z3JhIEVCRyBBLlMuMR0wGwYDVQQLExRFLVR1Z3Jh -IFRydXN0IENlbnRlcjEmMCQGA1UEAxMdRS1UdWdyYSBHbG9iYWwgUm9vdCBDQSBF -Q0MgdjMwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASOmCm/xxAeJ9urA8woLNheSBkQ -KczLWYHMjLiSF4mDKpL2w6QdTGLVn9agRtwcvHbB40fQWxPa56WzZkjnIZpKT4YK -fWzqTTKACrJ6CZtpS5iB4i7sAnCWH/31Rs7K3IKjYzBhMA8GA1UdEwEB/wQFMAMB -Af8wHwYDVR0jBBgwFoAU/4Ixcj75xGZsrTie0bBRiKWQzPUwHQYDVR0OBBYEFP+C -MXI++cRmbK04ntGwUYilkMz1MA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNp -ADBmAjEA5gVYaWHlLcoNy/EZCL3W/VGSGn5jVASQkZo1kTmZ+gepZpO6yGjUij/6 -7W4WAie3AjEA3VoXK3YdZUKWpqxdinlW2Iob35reX8dQj7FbcQwm32pAAOwzkSFx -vmjkI6TZraE3 ------END CERTIFICATE----- - # Issuer: CN=Security Communication RootCA3 O=SECOM Trust Systems CO.,LTD. # Subject: CN=Security Communication RootCA3 O=SECOM Trust Systems CO.,LTD. # Label: "Security Communication RootCA3" @@ -4525,3 +4386,250 @@ 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----- diff --git a/shotgun_api3/lib/requirements.txt b/shotgun_api3/lib/requirements.txt index c4b904c77..08bf3f4b7 100644 --- a/shotgun_api3/lib/requirements.txt +++ b/shotgun_api3/lib/requirements.txt @@ -30,5 +30,5 @@ # released for our dependencies. httplib2==0.19.1 six==1.13.0 -certifi==2022.12.7 +certifi==2023.7.22 pyparsing==2.4.7 \ No newline at end of file From 1f132f8f495333acc7303996a8151d73b2c204ae Mon Sep 17 00:00:00 2001 From: Carlos Villavicencio <123113322+carlos-villavicencio-adsk@users.noreply.github.com> Date: Tue, 29 Aug 2023 15:45:28 -0500 Subject: [PATCH 024/125] Packaging for release v3.3.6 (#286) * Packaging for v3.3.6 * Bump version to v3.3.6.dev * Bump version to v3.3.6.dev * Update version strings --- HISTORY.rst | 12 ++++++++++++ setup.py | 2 +- shotgun_api3/shotgun.py | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index a03d3f40f..abbd9d8ab 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -4,6 +4,18 @@ ShotGrid Python API Changelog Here you can see the full list of changes between each Python API release. +v3.3.6 (2023 Aug 29) +==================== +- Update docs for entity fields. +- Fix typo. +- Fix incorrect hint. +- Reformat code examples to prevent text overflow. +- Bump certifi from 2020.06.20 to 2022.12.7 in /shotgun_api3/lib. +- Skip SG-MIM entities. +- Replace shotgunsoftware references. +- Deprecation of Python 2. +- Security upgrade certifi to latest version 2023.07.22. + v3.3.5 (2023 Jan 5) ==================== - Add "Setting Up Your Environment with the Python API" to Python Docs (python-api docs). diff --git a/setup.py b/setup.py index 6462d1781..37b56dbae 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ setup( name='shotgun_api3', - version='3.3.5', + version='3.3.6', description='Shotgun Python API ', long_description=readme, author='Shotgun Software', diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index 27a2b0e82..00087a5a7 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -122,7 +122,7 @@ def _is_mimetypes_broken(): # ---------------------------------------------------------------------------- # Version -__version__ = "3.3.5" +__version__ = "3.3.6" # ---------------------------------------------------------------------------- # Errors From 79077875f50cc11cf46e901418a1a769f644d515 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Sauv=C3=A9?= <119680820+dsauve-adsk@users.noreply.github.com> Date: Thu, 31 Aug 2023 11:32:44 -0400 Subject: [PATCH 025/125] SG-31392 Add doc for PublishedFiles preset filters (#292) * SG-31392 Add doc for PublishedFiles preset filters * SG-31392 grammar fixes --- docs/reference.rst | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/docs/reference.rst b/docs/reference.rst index 47a27e9c6..90c523161 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -545,9 +545,9 @@ with which parameters. LATEST [string] latest_by 'ENTITIES_CREATED_AT': When dealing with multiple Versions associated to a group - of entities, returns only the - last Version created for each - entity. + of entities, this will return + only the last Version created + for each entity. For example, when dealing with a set of Shots, this preset allows to find the latest Version created @@ -570,6 +570,23 @@ with which parameters. will return the Version associated to the Task with the highest step.list_order value. + Published LATEST [string] latest_by 'ENTITIES_CREATED_AT': + Files When dealing with multiple + PublishedFiles associated to a + group of entities, this will return + only the last PublishedFiles created + for each entity. + For example, when dealing with a + set of Versions, this preset allows + you to find the latest PublishedFile + created for each of these Versions. + + 'VERSION_NUMBER': + When dealing with multiple + PublishedFiles associated with a + group of entities, this returns only + the PublishedFile with the highest + version_number. .. _data_types: From 21755150c4914fa0a8c789650488dd10fc488a28 Mon Sep 17 00:00:00 2001 From: Carlos Villavicencio <123113322+carlos-villavicencio-adsk@users.noreply.github.com> Date: Mon, 18 Sep 2023 11:12:20 -0500 Subject: [PATCH 026/125] Upgrade httplib2 to 0.22.0 (#305) --- shotgun_api3/lib/httplib2/auth.py | 69 + shotgun_api3/lib/httplib2/cacerts.txt | 2225 +++++++++++++++++ shotgun_api3/lib/httplib2/certs.py | 42 + shotgun_api3/lib/httplib2/error.py | 48 + shotgun_api3/lib/httplib2/iri2uri.py | 124 + shotgun_api3/lib/httplib2/python2/__init__.py | 36 +- shotgun_api3/lib/httplib2/python2/cacerts.txt | 85 +- shotgun_api3/lib/httplib2/python3/__init__.py | 92 +- shotgun_api3/lib/httplib2/python3/auth.py | 8 +- shotgun_api3/lib/httplib2/python3/cacerts.txt | 84 +- shotgun_api3/lib/httplib2/socks.py | 518 ++++ shotgun_api3/lib/requirements.txt | 2 +- 12 files changed, 3211 insertions(+), 122 deletions(-) create mode 100644 shotgun_api3/lib/httplib2/auth.py create mode 100644 shotgun_api3/lib/httplib2/cacerts.txt create mode 100644 shotgun_api3/lib/httplib2/certs.py create mode 100644 shotgun_api3/lib/httplib2/error.py create mode 100644 shotgun_api3/lib/httplib2/iri2uri.py create mode 100644 shotgun_api3/lib/httplib2/socks.py diff --git a/shotgun_api3/lib/httplib2/auth.py b/shotgun_api3/lib/httplib2/auth.py new file mode 100644 index 000000000..53f427be1 --- /dev/null +++ b/shotgun_api3/lib/httplib2/auth.py @@ -0,0 +1,69 @@ +import base64 +import re + +from ... import pyparsing as pp + +from .error import * + + +try: # pyparsing>=3.0.0 + downcaseTokens = pp.common.downcaseTokens +except AttributeError: + downcaseTokens = pp.downcaseTokens + +UNQUOTE_PAIRS = re.compile(r"\\(.)") +unquote = lambda s, l, t: UNQUOTE_PAIRS.sub(r"\1", t[0][1:-1]) + +# https://tools.ietf.org/html/rfc7235#section-1.2 +# https://tools.ietf.org/html/rfc7235#appendix-B +tchar = "!#$%&'*+-.^_`|~" + pp.nums + pp.alphas +token = pp.Word(tchar).setName("token") +token68 = pp.Combine(pp.Word("-._~+/" + pp.nums + pp.alphas) + pp.Optional(pp.Word("=").leaveWhitespace())).setName( + "token68" +) + +quoted_string = pp.dblQuotedString.copy().setName("quoted-string").setParseAction(unquote) +auth_param_name = token.copy().setName("auth-param-name").addParseAction(downcaseTokens) +auth_param = auth_param_name + pp.Suppress("=") + (quoted_string | token) +params = pp.Dict(pp.delimitedList(pp.Group(auth_param))) + +scheme = token("scheme") +challenge = scheme + (params("params") | token68("token")) + +authentication_info = params.copy() +www_authenticate = pp.delimitedList(pp.Group(challenge)) + + +def _parse_authentication_info(headers, headername="authentication-info"): + """https://tools.ietf.org/html/rfc7615 + """ + header = headers.get(headername, "").strip() + if not header: + return {} + try: + parsed = authentication_info.parseString(header) + except pp.ParseException as ex: + # print(ex.explain(ex)) + raise MalformedHeader(headername) + + return parsed.asDict() + + +def _parse_www_authenticate(headers, headername="www-authenticate"): + """Returns a dictionary of dictionaries, one dict per auth_scheme.""" + header = headers.get(headername, "").strip() + if not header: + return {} + try: + parsed = www_authenticate.parseString(header) + except pp.ParseException as ex: + # print(ex.explain(ex)) + raise MalformedHeader(headername) + + retval = { + challenge["scheme"].lower(): challenge["params"].asDict() + if "params" in challenge + else {"token": challenge.get("token")} + for challenge in parsed + } + return retval diff --git a/shotgun_api3/lib/httplib2/cacerts.txt b/shotgun_api3/lib/httplib2/cacerts.txt new file mode 100644 index 000000000..78a444c43 --- /dev/null +++ b/shotgun_api3/lib/httplib2/cacerts.txt @@ -0,0 +1,2225 @@ +# Issuer: CN=GTE CyberTrust Global Root O=GTE Corporation OU=GTE CyberTrust Solutions, Inc. +# Subject: CN=GTE CyberTrust Global Root O=GTE Corporation OU=GTE CyberTrust Solutions, Inc. +# Label: "GTE CyberTrust Global Root" +# Serial: 421 +# MD5 Fingerprint: ca:3d:d3:68:f1:03:5c:d0:32:fa:b8:2b:59:e8:5a:db +# SHA1 Fingerprint: 97:81:79:50:d8:1c:96:70:cc:34:d8:09:cf:79:44:31:36:7e:f4:74 +# SHA256 Fingerprint: a5:31:25:18:8d:21:10:aa:96:4b:02:c7:b7:c6:da:32:03:17:08:94:e5:fb:71:ff:fb:66:67:d5:e6:81:0a:36 +-----BEGIN CERTIFICATE----- +MIICWjCCAcMCAgGlMA0GCSqGSIb3DQEBBAUAMHUxCzAJBgNVBAYTAlVTMRgwFgYD +VQQKEw9HVEUgQ29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNv +bHV0aW9ucywgSW5jLjEjMCEGA1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJv +b3QwHhcNOTgwODEzMDAyOTAwWhcNMTgwODEzMjM1OTAwWjB1MQswCQYDVQQGEwJV +UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU +cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds +b2JhbCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVD6C28FCc6HrH +iM3dFw4usJTQGz0O9pTAipTHBsiQl8i4ZBp6fmw8U+E3KHNgf7KXUwefU/ltWJTS +r41tiGeA5u2ylc9yMcqlHHK6XALnZELn+aks1joNrI1CqiQBOeacPwGFVw1Yh0X4 +04Wqk2kmhXBIgD8SFcd5tB8FLztimQIDAQABMA0GCSqGSIb3DQEBBAUAA4GBAG3r +GwnpXtlR22ciYaQqPEh346B8pt5zohQDhT37qw4wxYMWM4ETCJ57NE7fQMh017l9 +3PR2VX2bY1QY6fDq81yx2YtCHrnAlU66+tXifPVoYb+O7AWXX1uw16OFNMQkpw0P +lZPvy5TYnh+dXIVtx6quTx8itc2VrbqnzPmrC3p/ +-----END CERTIFICATE----- + +# Issuer: CN=Thawte Server CA O=Thawte Consulting cc OU=Certification Services Division +# Subject: CN=Thawte Server CA O=Thawte Consulting cc OU=Certification Services Division +# Label: "Thawte Server CA" +# Serial: 1 +# MD5 Fingerprint: c5:70:c4:a2:ed:53:78:0c:c8:10:53:81:64:cb:d0:1d +# SHA1 Fingerprint: 23:e5:94:94:51:95:f2:41:48:03:b4:d5:64:d2:a3:a3:f5:d8:8b:8c +# SHA256 Fingerprint: b4:41:0b:73:e2:e6:ea:ca:47:fb:c4:2f:8f:a4:01:8a:f4:38:1d:c5:4c:fa:a8:44:50:46:1e:ed:09:45:4d:e9 +-----BEGIN CERTIFICATE----- +MIIDEzCCAnygAwIBAgIBATANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMCWkEx +FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD +VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv +biBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEm +MCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wHhcNOTYwODAx +MDAwMDAwWhcNMjAxMjMxMjM1OTU5WjCBxDELMAkGA1UEBhMCWkExFTATBgNVBAgT +DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3 +dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNl +cyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3 +DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQAD +gY0AMIGJAoGBANOkUG7I/1Zr5s9dtuoMaHVHoqrC2oQl/Kj0R1HahbUgdJSGHg91 +yekIYfUGbTBuFRkC6VLAYttNmZ7iagxEOM3+vuNkCXDF/rFrKbYvScg71CcEJRCX +L+eQbcAoQpnXTEPew/UhbVSfXcNY4cDk2VuwuNy0e982OsK1ZiIS1ocNAgMBAAGj +EzARMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAB/pMaVz7lcxG +7oWDTSEwjsrZqG9JGubaUeNgcGyEYRGhGshIPllDfU+VPaGLtwtimHp1it2ITk6e +QNuozDJ0uW8NxuOzRAvZim+aKZuZGCg70eNAKJpaPNW15yAbi8qkq43pUdniTCxZ +qdq5snUb9kLy78fyGPmJvKP/iiMucEc= +-----END CERTIFICATE----- + +# Issuer: CN=Thawte Premium Server CA O=Thawte Consulting cc OU=Certification Services Division +# Subject: CN=Thawte Premium Server CA O=Thawte Consulting cc OU=Certification Services Division +# Label: "Thawte Premium Server CA" +# Serial: 1 +# MD5 Fingerprint: 06:9f:69:79:16:66:90:02:1b:8c:8c:a2:c3:07:6f:3a +# SHA1 Fingerprint: 62:7f:8d:78:27:65:63:99:d2:7d:7f:90:44:c9:fe:b3:f3:3e:fa:9a +# SHA256 Fingerprint: ab:70:36:36:5c:71:54:aa:29:c2:c2:9f:5d:41:91:16:3b:16:2a:22:25:01:13:57:d5:6d:07:ff:a7:bc:1f:72 +-----BEGIN CERTIFICATE----- +MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMCWkEx +FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD +VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv +biBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhhd3RlIFByZW1pdW0gU2Vy +dmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNlcnZlckB0aGF3dGUuY29t +MB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgc4xCzAJBgNVBAYTAlpB +MRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEdMBsG +A1UEChMUVGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNVBAsTH0NlcnRpZmljYXRp +b24gU2VydmljZXMgRGl2aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNl +cnZlciBDQTEoMCYGCSqGSIb3DQEJARYZcHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNv +bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2aovXwlue2oFBYo847kkE +VdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560ZXUCTe/LCaIhUdib0GfQ +ug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j+ao6hnO2RlNYyIkFvYMR +uHM/qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG +9w0BAQQFAAOBgQAmSCwWwlj66BZ0DKqqX1Q/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUI +hfzJATj/Tb7yFkJD57taRvvBxhEf8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZa4JM +pAwSremkrj/xw0llmozFyD4lt5SZu5IycQfwhl7tUCemDaYj+bvLpgcUQg== +-----END CERTIFICATE----- + +# Issuer: O=Equifax OU=Equifax Secure Certificate Authority +# Subject: O=Equifax OU=Equifax Secure Certificate Authority +# Label: "Equifax Secure CA" +# Serial: 903804111 +# MD5 Fingerprint: 67:cb:9d:c0:13:24:8a:82:9b:b2:17:1e:d1:1b:ec:d4 +# SHA1 Fingerprint: d2:32:09:ad:23:d3:14:23:21:74:e4:0d:7f:9d:62:13:97:86:63:3a +# SHA256 Fingerprint: 08:29:7a:40:47:db:a2:36:80:c7:31:db:6e:31:76:53:ca:78:48:e1:be:bd:3a:0b:01:79:a7:07:f9:2c:f1:78 +-----BEGIN CERTIFICATE----- +MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV +UzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2Vy +dGlmaWNhdGUgQXV0aG9yaXR5MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1 +MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoTB0VxdWlmYXgxLTArBgNVBAsTJEVx +dWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCBnzANBgkqhkiG9w0B +AQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPRfM6f +BeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+A +cJkVV5MW8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kC +AwEAAaOCAQkwggEFMHAGA1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQ +MA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlm +aWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTgw +ODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvSspXXR9gj +IBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQF +MAMBAf8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUA +A4GBAFjOKer89961zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y +7qj/WsjTVbJmcVfewCHrPSqnI0kBBIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh +1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee9570+sB3c4 +-----END CERTIFICATE----- + +# Issuer: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority - G2/(c) 1998 VeriSign, Inc. - For authorized use only/VeriSign Trust Network +# Subject: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority - G2/(c) 1998 VeriSign, Inc. - For authorized use only/VeriSign Trust Network +# Label: "Verisign Class 3 Public Primary Certification Authority - G2" +# Serial: 167285380242319648451154478808036881606 +# MD5 Fingerprint: a2:33:9b:4c:74:78:73:d4:6c:e7:c1:f3:8d:cb:5c:e9 +# SHA1 Fingerprint: 85:37:1c:a6:e5:50:14:3d:ce:28:03:47:1b:de:3a:09:e8:f8:77:0f +# SHA256 Fingerprint: 83:ce:3c:12:29:68:8a:59:3d:48:5f:81:97:3c:0f:91:95:43:1e:da:37:cc:5e:36:43:0e:79:c7:a8:88:63:8b +-----BEGIN CERTIFICATE----- +MIIDAjCCAmsCEH3Z/gfPqB63EHln+6eJNMYwDQYJKoZIhvcNAQEFBQAwgcExCzAJ +BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xh +c3MgMyBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcy +MTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3Jp +emVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMB4X +DTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVTMRcw +FQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMg +UHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEo +YykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5 +MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEB +AQUAA4GNADCBiQKBgQDMXtERXVxp0KvTuWpMmR9ZmDCOFoUgRm1HP9SFIIThbbP4 +pO0M8RcPO/mn+SXXwc+EY/J8Y8+iR/LGWzOOZEAEaMGAuWQcRXfH2G71lSk8UOg0 +13gfqLptQ5GVj0VXXn7F+8qkBOvqlzdUMG+7AUcyM83cV5tkaWH4mx0ciU9cZwID +AQABMA0GCSqGSIb3DQEBBQUAA4GBAFFNzb5cy5gZnBWyATl4Lk0PZ3BwmcYQWpSk +U01UbSuvDV1Ai2TT1+7eVmGSX6bEHRBhNtMsJzzoKQm5EWR0zLVznxxIqbxhAe7i +F6YM40AIOw7n60RzKprxaZLvcRTDOaxxp5EJb+RxBrO6WVcmeQD2+A2iMzAo1KpY +oJ2daZH9 +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA +# Subject: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA +# Label: "GlobalSign Root CA" +# Serial: 4835703278459707669005204 +# MD5 Fingerprint: 3e:45:52:15:09:51:92:e1:b7:5d:37:9f:b1:87:29:8a +# SHA1 Fingerprint: b1:bc:96:8b:d4:f4:9d:62:2a:a8:9a:81:f2:15:01:52:a4:1d:82:9c +# SHA256 Fingerprint: eb:d4:10:40:e4:bb:3e:c7:42:c9:e3:81:d3:1e:f2:a4:1a:48:b6:68:5c:96:e7:ce:f3:c1:df:6c:d4:33:1c:99 +-----BEGIN CERTIFICATE----- +MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG +A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv +b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw +MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i +YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT +aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ +jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp +xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp +1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG +snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ +U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8 +9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B +AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz +yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE +38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP +AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad +DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME +HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R2 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R2 +# Label: "GlobalSign Root CA - R2" +# Serial: 4835703278459682885658125 +# MD5 Fingerprint: 94:14:77:7e:3e:5e:fd:8f:30:bd:41:b0:cf:e7:d0:30 +# SHA1 Fingerprint: 75:e0:ab:b6:13:85:12:27:1c:04:f8:5f:dd:de:38:e4:b7:24:2e:fe +# SHA256 Fingerprint: ca:42:dd:41:74:5f:d0:b8:1e:b9:02:36:2c:f9:d8:bf:71:9d:a1:bd:1b:1e:fc:94:6f:5b:4c:99:f4:2c:1b:9e +-----BEGIN CERTIFICATE----- +MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4G +A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNp +Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1 +MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEG +A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6ErPL +v4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8 +eoLrvozps6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklq +tTleiDTsvHgMCJiEbKjNS7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzd +C9XZzPnqJworc5HGnRusyMvo4KD0L5CLTfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pa +zq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6CygPCm48CAwEAAaOBnDCB +mTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUm+IH +V2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5n +bG9iYWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG +3lm0mi3f3BmGLjANBgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4Gs +J0/WwbgcQ3izDJr86iw8bmEbTUsp9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO +291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu01yiPqFbQfXf5WRDLenVOavS +ot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG79G+dwfCMNYxd +AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7 +TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg== +-----END CERTIFICATE----- + +# Issuer: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 1 Policy Validation Authority +# Subject: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 1 Policy Validation Authority +# Label: "ValiCert Class 1 VA" +# Serial: 1 +# MD5 Fingerprint: 65:58:ab:15:ad:57:6c:1e:a8:a7:b5:69:ac:bf:ff:eb +# SHA1 Fingerprint: e5:df:74:3c:b6:01:c4:9b:98:43:dc:ab:8c:e8:6a:81:10:9f:e4:8e +# SHA256 Fingerprint: f4:c1:49:55:1a:30:13:a3:5b:c7:bf:fe:17:a7:f3:44:9b:c1:ab:5b:5a:0a:e7:4b:06:c2:3b:90:00:4c:01:04 +-----BEGIN CERTIFICATE----- +MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0 +IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz +BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y +aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG +9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNTIyMjM0OFoXDTE5MDYy +NTIyMjM0OFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y +azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs +YXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw +Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl +cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYWYJ6ibiWuqYvaG9Y +LqdUHAZu9OqNSLwxlBfw8068srg1knaw0KWlAdcAAxIiGQj4/xEjm84H9b9pGib+ +TunRf50sQB1ZaG6m+FiwnRqP0z/x3BkGgagO4DrdyFNFCQbmD3DD+kCmDuJWBQ8Y +TfwggtFzVXSNdnKgHZ0dwN0/cQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFBoPUn0 +LBwGlN+VYH+Wexf+T3GtZMjdd9LvWVXoP+iOBSoh8gfStadS/pyxtuJbdxdA6nLW +I8sogTLDAHkY7FkXicnGah5xyf23dKUlRWnFSKsZ4UWKJWsZ7uW7EvV/96aNUcPw +nXS3qT6gpf+2SQMT2iLM7XGCK5nPOrf1LXLI +-----END CERTIFICATE----- + +# Issuer: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 2 Policy Validation Authority +# Subject: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 2 Policy Validation Authority +# Label: "ValiCert Class 2 VA" +# Serial: 1 +# MD5 Fingerprint: a9:23:75:9b:ba:49:36:6e:31:c2:db:f2:e7:66:ba:87 +# SHA1 Fingerprint: 31:7a:2a:d0:7f:2b:33:5e:f5:a1:c3:4e:4b:57:e8:b7:d8:f1:fc:a6 +# SHA256 Fingerprint: 58:d0:17:27:9c:d4:dc:63:ab:dd:b1:96:a6:c9:90:6c:30:c4:e0:87:83:ea:e8:c1:60:99:54:d6:93:55:59:6b +-----BEGIN CERTIFICATE----- +MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0 +IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz +BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y +aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG +9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMTk1NFoXDTE5MDYy +NjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y +azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs +YXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw +Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl +cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOOnHK5avIWZJV16vY +dA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVCCSRrCl6zfN1SLUzm1NZ9 +WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7RfZHM047QS +v4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9v +UJSZSWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTu +IYEZoDJJKPTEjlbVUjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwC +W/POuZ6lcg5Ktz885hZo+L7tdEy8W9ViH0Pd +-----END CERTIFICATE----- + +# Issuer: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 3 Policy Validation Authority +# Subject: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 3 Policy Validation Authority +# Label: "RSA Root Certificate 1" +# Serial: 1 +# MD5 Fingerprint: a2:6f:53:b7:ee:40:db:4a:68:e7:fa:18:d9:10:4b:72 +# SHA1 Fingerprint: 69:bd:8c:f4:9c:d3:00:fb:59:2e:17:93:ca:55:6a:f3:ec:aa:35:fb +# SHA256 Fingerprint: bc:23:f9:8a:31:3c:b9:2d:e3:bb:fc:3a:5a:9f:44:61:ac:39:49:4c:4a:e1:5a:9e:9d:f1:31:e9:9b:73:01:9a +-----BEGIN CERTIFICATE----- +MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0 +IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz +BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y +aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG +9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMjIzM1oXDTE5MDYy +NjAwMjIzM1owgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y +azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs +YXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw +Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl +cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDjmFGWHOjVsQaBalfD +cnWTq8+epvzzFlLWLU2fNUSoLgRNB0mKOCn1dzfnt6td3zZxFJmP3MKS8edgkpfs +2Ejcv8ECIMYkpChMMFp2bbFc893enhBxoYjHW5tBbcqwuI4V7q0zK89HBFx1cQqY +JJgpp0lZpd34t0NiYfPT4tBVPwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFa7AliE +Zwgs3x/be0kz9dNnnfS0ChCzycUs4pJqcXgn8nCDQtM+z6lU9PHYkhaM0QTLS6vJ +n0WuPIqpsHEzXcjFV9+vqDWzf4mH6eglkrh/hXqu1rweN1gqZ8mRzyqBPu3GOd/A +PhmcGcwTTYJBtYze4D1gCCAPRX5ron+jjBXu +-----END CERTIFICATE----- + +# Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only +# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only +# Label: "Verisign Class 3 Public Primary Certification Authority - G3" +# Serial: 206684696279472310254277870180966723415 +# MD5 Fingerprint: cd:68:b6:a7:c7:c4:ce:75:e0:1d:4f:57:44:61:92:09 +# SHA1 Fingerprint: 13:2d:0d:45:53:4b:69:97:cd:b2:d5:c3:39:e2:55:76:60:9b:5c:c6 +# SHA256 Fingerprint: eb:04:cf:5e:b1:f3:9a:fa:76:2f:2b:b1:20:f2:96:cb:a5:20:c1:b9:7d:b1:58:95:65:b8:1c:b9:a1:7b:72:44 +-----BEGIN CERTIFICATE----- +MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQsw +CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl +cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu +LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT +aWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp +dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD +VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT +aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ +bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu +IENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg +LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMu6nFL8eB8aHm8b +N3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1EUGO+i2t +KmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGu +kxUccLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBm +CC+Vk7+qRy+oRpfwEuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJ +Xwzw3sJ2zq/3avL6QaaiMxTJ5Xpj055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWu +imi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAERSWwauSCPc/L8my/uRan2Te +2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5fj267Cz3qWhMe +DGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC +/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565p +F4ErWjfJXir0xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGt +TxzhT5yvDwyd93gN2PQ1VoDat20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ== +-----END CERTIFICATE----- + +# Issuer: CN=VeriSign Class 4 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only +# Subject: CN=VeriSign Class 4 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only +# Label: "Verisign Class 4 Public Primary Certification Authority - G3" +# Serial: 314531972711909413743075096039378935511 +# MD5 Fingerprint: db:c8:f2:27:2e:b1:ea:6a:29:23:5d:fe:56:3e:33:df +# SHA1 Fingerprint: c8:ec:8c:87:92:69:cb:4b:ab:39:e9:8d:7e:57:67:f3:14:95:73:9d +# SHA256 Fingerprint: e3:89:36:0d:0f:db:ae:b3:d2:50:58:4b:47:30:31:4e:22:2f:39:c1:56:a0:20:14:4e:8d:96:05:61:79:15:06 +-----BEGIN CERTIFICATE----- +MIIEGjCCAwICEQDsoKeLbnVqAc/EfMwvlF7XMA0GCSqGSIb3DQEBBQUAMIHKMQsw +CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl +cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu +LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT +aWduIENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp +dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD +VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT +aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ +bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu +IENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg +LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK3LpRFpxlmr8Y+1 +GQ9Wzsy1HyDkniYlS+BzZYlZ3tCD5PUPtbut8XzoIfzk6AzufEUiGXaStBO3IFsJ ++mGuqPKljYXCKtbeZjbSmwL0qJJgfJxptI8kHtCGUvYynEFYHiK9zUVilQhu0Gbd +U6LM8BDcVHOLBKFGMzNcF0C5nk3T875Vg+ixiY5afJqWIpA7iCXy0lOIAgwLePLm +NxdLMEYH5IBtptiWLugs+BGzOA1mppvqySNb247i8xOOGlktqgLw7KSHZtzBP/XY +ufTsgsbSPZUd5cBPhMnZo0QoBmrXRazwa2rvTl/4EYIeOGM0ZlDUPpNz+jDDZq3/ +ky2X7wMCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAj/ola09b5KROJ1WrIhVZPMq1 +CtRK26vdoV9TxaBXOcLORyu+OshWv8LZJxA6sQU8wHcxuzrTBXttmhwwjIDLk5Mq +g6sFUYICABFna/OIYUdfA5PVWw3g8dShMjWFsjrbsIKr0csKvE+MW8VLADsfKoKm +fjaF3H48ZwC15DtS4KjrXRX5xm3wrR0OhbepmnMUWluPQSjA1egtTaRezarZ7c7c +2NU8Qh0XwRJdRTjDOPP8hS6DRkiy1yBfkjaP53kPmF6Z6PDQpLv1U70qzlmwr25/ +bLvSHgCwIe34QWKCudiyxLtGUPMxxY8BqHTr9Xgn2uf3ZkPznoM+IKrDNWCRzg== +-----END CERTIFICATE----- + +# Issuer: CN=Entrust.net Secure Server Certification Authority O=Entrust.net OU=www.entrust.net/CPS incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited +# Subject: CN=Entrust.net Secure Server Certification Authority O=Entrust.net OU=www.entrust.net/CPS incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited +# Label: "Entrust.net Secure Server CA" +# Serial: 927650371 +# MD5 Fingerprint: df:f2:80:73:cc:f1:e6:61:73:fc:f5:42:e9:c5:7c:ee +# SHA1 Fingerprint: 99:a6:9b:e6:1a:fe:88:6b:4d:2b:82:00:7c:b8:54:fc:31:7e:15:39 +# SHA256 Fingerprint: 62:f2:40:27:8c:56:4c:4d:d8:bf:7d:9d:4f:6f:36:6e:a8:94:d2:2f:5f:34:d9:89:a9:83:ac:ec:2f:ff:ed:50 +-----BEGIN CERTIFICATE----- +MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC +VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u +ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc +KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u +ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw05OTA1 +MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIGA1UE +ChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5j +b3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF +bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUg +U2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUA +A4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQaO2f55M28Qpku0f1BBc/ +I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5gXpa0zf3 +wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OC +AdcwggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHb +oIHYpIHVMIHSMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5 +BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1p +dHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1pdGVk +MTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRp +b24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu +dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0 +MFqBDzIwMTkwNTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8Bdi +E1U9s/8KAGv7UISX8+1i0BowHQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAa +MAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EABAwwChsEVjQuMAMCBJAwDQYJKoZI +hvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyNEwr75Ji174z4xRAN +95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9n9cd +2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI= +-----END CERTIFICATE----- + +# Issuer: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited +# Subject: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited +# Label: "Entrust.net Premium 2048 Secure Server CA" +# Serial: 946059622 +# MD5 Fingerprint: ba:21:ea:20:d6:dd:db:8f:c1:57:8b:40:ad:a1:fc:fc +# SHA1 Fingerprint: 80:1d:62:d0:7b:44:9d:5c:5c:03:5c:98:ea:61:fa:44:3c:2a:58:fe +# SHA256 Fingerprint: d1:c3:39:ea:27:84:eb:87:0f:93:4f:c5:63:4e:4a:a9:ad:55:05:01:64:01:f2:64:65:d3:7a:57:46:63:35:9f +-----BEGIN CERTIFICATE----- +MIIEXDCCA0SgAwIBAgIEOGO5ZjANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML +RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp +bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5 +IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQxNzUwNTFaFw0xOTEy +MjQxODIwNTFaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3 +LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxp +YWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEG +A1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArU1LqRKGsuqjIAcVFmQq +K0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOLGp18EzoOH1u3Hs/lJBQe +sYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSrhRSGlVuX +MlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVT +XTzWnLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/ +HoZdenoVve8AjhUiVBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH +4QIDAQABo3QwcjARBglghkgBhvhCAQEEBAMCAAcwHwYDVR0jBBgwFoAUVeSB0RGA +vtiJuQijMfmhJAkWuXAwHQYDVR0OBBYEFFXkgdERgL7YibkIozH5oSQJFrlwMB0G +CSqGSIb2fQdBAAQQMA4bCFY1LjA6NC4wAwIEkDANBgkqhkiG9w0BAQUFAAOCAQEA +WUesIYSKF8mciVMeuoCFGsY8Tj6xnLZ8xpJdGGQC49MGCBFhfGPjK50xA3B20qMo +oPS7mmNz7W3lKtvtFKkrxjYR0CvrB4ul2p5cGZ1WEvVUKcgF7bISKo30Axv/55IQ +h7A6tcOdBTcSo8f0FbnVpDkWm1M6I5HxqIKiaohowXkCIryqptau37AUX7iH0N18 +f3v/rxzP5tsHrV7bhZ3QKw0z2wTR5klAEyt2+z7pnIkPFc4YsIV4IU9rTw76NmfN +B/L/CNDi3tm/Kq+4h4YhPATKt5Rof8886ZjXOP/swNlQ8C5LWK5Gb9Auw2DaclVy +vUxFnmG6v4SBkgPR0ml8xQ== +-----END CERTIFICATE----- + +# Issuer: CN=Baltimore CyberTrust Root O=Baltimore OU=CyberTrust +# Subject: CN=Baltimore CyberTrust Root O=Baltimore OU=CyberTrust +# Label: "Baltimore CyberTrust Root" +# Serial: 33554617 +# MD5 Fingerprint: ac:b6:94:a5:9c:17:e0:d7:91:52:9b:b1:97:06:a6:e4 +# SHA1 Fingerprint: d4:de:20:d0:5e:66:fc:53:fe:1a:50:88:2c:78:db:28:52:ca:e4:74 +# SHA256 Fingerprint: 16:af:57:a9:f6:76:b0:ab:12:60:95:aa:5e:ba:de:f2:2a:b3:11:19:d6:44:ac:95:cd:4b:93:db:f3:f2:6a:eb +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ +RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD +VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX +DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y +ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy +VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr +mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr +IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK +mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu +XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy +dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye +jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1 +BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3 +DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92 +9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx +jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0 +Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz +ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS +R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp +-----END CERTIFICATE----- + +# Issuer: CN=Equifax Secure Global eBusiness CA-1 O=Equifax Secure Inc. +# Subject: CN=Equifax Secure Global eBusiness CA-1 O=Equifax Secure Inc. +# Label: "Equifax Secure Global eBusiness CA" +# Serial: 1 +# MD5 Fingerprint: 8f:5d:77:06:27:c4:98:3c:5b:93:78:e7:d7:7d:9b:cc +# SHA1 Fingerprint: 7e:78:4a:10:1c:82:65:cc:2d:e1:f1:6d:47:b4:40:ca:d9:0a:19:45 +# SHA256 Fingerprint: 5f:0b:62:ea:b5:e3:53:ea:65:21:65:16:58:fb:b6:53:59:f4:43:28:0a:4a:fb:d1:04:d7:7d:10:f9:f0:4c:07 +-----BEGIN CERTIFICATE----- +MIICkDCCAfmgAwIBAgIBATANBgkqhkiG9w0BAQQFADBaMQswCQYDVQQGEwJVUzEc +MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEtMCsGA1UEAxMkRXF1aWZheCBT +ZWN1cmUgR2xvYmFsIGVCdXNpbmVzcyBDQS0xMB4XDTk5MDYyMTA0MDAwMFoXDTIw +MDYyMTA0MDAwMFowWjELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0VxdWlmYXggU2Vj +dXJlIEluYy4xLTArBgNVBAMTJEVxdWlmYXggU2VjdXJlIEdsb2JhbCBlQnVzaW5l +c3MgQ0EtMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAuucXkAJlsTRVPEnC +UdXfp9E3j9HngXNBUmCbnaEXJnitx7HoJpQytd4zjTov2/KaelpzmKNc6fuKcxtc +58O/gGzNqfTWK8D3+ZmqY6KxRwIP1ORROhI8bIpaVIRw28HFkM9yRcuoWcDNM50/ +o5brhTMhHD4ePmBudpxnhcXIw2ECAwEAAaNmMGQwEQYJYIZIAYb4QgEBBAQDAgAH +MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUvqigdHJQa0S3ySPY+6j/s1dr +aGwwHQYDVR0OBBYEFL6ooHRyUGtEt8kj2Puo/7NXa2hsMA0GCSqGSIb3DQEBBAUA +A4GBADDiAVGqx+pf2rnQZQ8w1j7aDRRJbpGTJxQx78T3LUX47Me/okENI7SS+RkA +Z70Br83gcfxaz2TE4JaY0KNA4gGK7ycH8WUBikQtBmV1UsCGECAhX2xrD2yuCRyv +8qIYNMR1pHMc8Y3c7635s3a0kr/clRAevsvIO1qEYBlWlKlV +-----END CERTIFICATE----- + +# Issuer: CN=Equifax Secure eBusiness CA-1 O=Equifax Secure Inc. +# Subject: CN=Equifax Secure eBusiness CA-1 O=Equifax Secure Inc. +# Label: "Equifax Secure eBusiness CA 1" +# Serial: 4 +# MD5 Fingerprint: 64:9c:ef:2e:44:fc:c6:8f:52:07:d0:51:73:8f:cb:3d +# SHA1 Fingerprint: da:40:18:8b:91:89:a3:ed:ee:ae:da:97:fe:2f:9d:f5:b7:d1:8a:41 +# SHA256 Fingerprint: cf:56:ff:46:a4:a1:86:10:9d:d9:65:84:b5:ee:b5:8a:51:0c:42:75:b0:e5:f9:4f:40:bb:ae:86:5e:19:f6:73 +-----BEGIN CERTIFICATE----- +MIICgjCCAeugAwIBAgIBBDANBgkqhkiG9w0BAQQFADBTMQswCQYDVQQGEwJVUzEc +MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBT +ZWN1cmUgZUJ1c2luZXNzIENBLTEwHhcNOTkwNjIxMDQwMDAwWhcNMjAwNjIxMDQw +MDAwWjBTMQswCQYDVQQGEwJVUzEcMBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5j +LjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNzIENBLTEwgZ8wDQYJ +KoZIhvcNAQEBBQADgY0AMIGJAoGBAM4vGbwXt3fek6lfWg0XTzQaDJj0ItlZ1MRo +RvC0NcWFAyDGr0WlIVFFQesWWDYyb+JQYmT5/VGcqiTZ9J2DKocKIdMSODRsjQBu +WqDZQu4aIZX5UkxVWsUPOE9G+m34LjXWHXzr4vCwdYDIqROsvojvOm6rXyo4YgKw +Env+j6YDAgMBAAGjZjBkMBEGCWCGSAGG+EIBAQQEAwIABzAPBgNVHRMBAf8EBTAD +AQH/MB8GA1UdIwQYMBaAFEp4MlIR21kWNl7fwRQ2QGpHfEyhMB0GA1UdDgQWBBRK +eDJSEdtZFjZe38EUNkBqR3xMoTANBgkqhkiG9w0BAQQFAAOBgQB1W6ibAxHm6VZM +zfmpTMANmvPMZWnmJXbMWbfWVMMdzZmsGd20hdXgPfxiIKeES1hl8eL5lSE/9dR+ +WB5Hh1Q+WKG1tfgq73HnvMP2sUlG4tega+VWeponmHxGYhTnyfxuAxJ5gDgdSIKN +/Bf+KpYrtWKmpj29f5JZzVoqgrI3eQ== +-----END CERTIFICATE----- + +# Issuer: O=Equifax Secure OU=Equifax Secure eBusiness CA-2 +# Subject: O=Equifax Secure OU=Equifax Secure eBusiness CA-2 +# Label: "Equifax Secure eBusiness CA 2" +# Serial: 930140085 +# MD5 Fingerprint: aa:bf:bf:64:97:da:98:1d:6f:c6:08:3a:95:70:33:ca +# SHA1 Fingerprint: 39:4f:f6:85:0b:06:be:52:e5:18:56:cc:10:e1:80:e8:82:b3:85:cc +# SHA256 Fingerprint: 2f:27:4e:48:ab:a4:ac:7b:76:59:33:10:17:75:50:6d:c3:0e:e3:8e:f6:ac:d5:c0:49:32:cf:e0:41:23:42:20 +-----BEGIN CERTIFICATE----- +MIIDIDCCAomgAwIBAgIEN3DPtTANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV +UzEXMBUGA1UEChMORXF1aWZheCBTZWN1cmUxJjAkBgNVBAsTHUVxdWlmYXggU2Vj +dXJlIGVCdXNpbmVzcyBDQS0yMB4XDTk5MDYyMzEyMTQ0NVoXDTE5MDYyMzEyMTQ0 +NVowTjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkVxdWlmYXggU2VjdXJlMSYwJAYD +VQQLEx1FcXVpZmF4IFNlY3VyZSBlQnVzaW5lc3MgQ0EtMjCBnzANBgkqhkiG9w0B +AQEFAAOBjQAwgYkCgYEA5Dk5kx5SBhsoNviyoynF7Y6yEb3+6+e0dMKP/wXn2Z0G +vxLIPw7y1tEkshHe0XMJitSxLJgJDR5QRrKDpkWNYmi7hRsgcDKqQM2mll/EcTc/ +BPO3QSQ5BxoeLmFYoBIL5aXfxavqN3HMHMg3OrmXUqesxWoklE6ce8/AatbfIb0C +AwEAAaOCAQkwggEFMHAGA1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEX +MBUGA1UEChMORXF1aWZheCBTZWN1cmUxJjAkBgNVBAsTHUVxdWlmYXggU2VjdXJl +IGVCdXNpbmVzcyBDQS0yMQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTkw +NjIzMTIxNDQ1WjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUUJ4L6q9euSBIplBq +y/3YIHqngnYwHQYDVR0OBBYEFFCeC+qvXrkgSKZQasv92CB6p4J2MAwGA1UdEwQF +MAMBAf8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUA +A4GBAAyGgq3oThr1jokn4jVYPSm0B482UJW/bsGe68SQsoWou7dC4A8HOd/7npCy +0cE+U58DRLB+S/Rv5Hwf5+Kx5Lia78O9zt4LMjTZ3ijtM2vE1Nc9ElirfQkty3D1 +E4qUoSek1nDFbZS1yX2doNLGCEnZZpum0/QL3MUmV+GRMOrN +-----END CERTIFICATE----- + +# Issuer: CN=AddTrust Class 1 CA Root O=AddTrust AB OU=AddTrust TTP Network +# Subject: CN=AddTrust Class 1 CA Root O=AddTrust AB OU=AddTrust TTP Network +# Label: "AddTrust Low-Value Services Root" +# Serial: 1 +# MD5 Fingerprint: 1e:42:95:02:33:92:6b:b9:5f:c0:7f:da:d6:b2:4b:fc +# SHA1 Fingerprint: cc:ab:0e:a0:4c:23:01:d6:69:7b:dd:37:9f:cd:12:eb:24:e3:94:9d +# SHA256 Fingerprint: 8c:72:09:27:9a:c0:4e:27:5e:16:d0:7f:d3:b7:75:e8:01:54:b5:96:80:46:e3:1f:52:dd:25:76:63:24:e9:a7 +-----BEGIN CERTIFICATE----- +MIIEGDCCAwCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQGEwJTRTEU +MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3 +b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwHhcNMDAwNTMw +MTAzODMxWhcNMjAwNTMwMTAzODMxWjBlMQswCQYDVQQGEwJTRTEUMBIGA1UEChML +QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYD +VQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUA +A4IBDwAwggEKAoIBAQCWltQhSWDia+hBBwzexODcEyPNwTXH+9ZOEQpnXvUGW2ul +CDtbKRY654eyNAbFvAWlA3yCyykQruGIgb3WntP+LVbBFc7jJp0VLhD7Bo8wBN6n +tGO0/7Gcrjyvd7ZWxbWroulpOj0OM3kyP3CCkplhbY0wCI9xP6ZIVxn4JdxLZlyl +dI+Yrsj5wAYi56xz36Uu+1LcsRVlIPo1Zmne3yzxbrww2ywkEtvrNTVokMsAsJch +PXQhI2U0K7t4WaPW4XY5mqRJjox0r26kmqPZm9I4XJuiGMx1I4S+6+JNM3GOGvDC ++Mcdoq0Dlyz4zyXG9rgkMbFjXZJ/Y/AlyVMuH79NAgMBAAGjgdIwgc8wHQYDVR0O +BBYEFJWxtPCUtr3H2tERCSG+wa9J/RB7MAsGA1UdDwQEAwIBBjAPBgNVHRMBAf8E +BTADAQH/MIGPBgNVHSMEgYcwgYSAFJWxtPCUtr3H2tERCSG+wa9J/RB7oWmkZzBl +MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFk +ZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENB +IFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBACxtZBsfzQ3duQH6lmM0MkhHma6X +7f1yFqZzR1r0693p9db7RcwpiURdv0Y5PejuvE1Uhh4dbOMXJ0PhiVYrqW9yTkkz +43J8KiOavD7/KCrto/8cI7pDVwlnTUtiBi34/2ydYB7YHEt9tTEv2dB8Xfjea4MY +eDdXL+gzB2ffHsdrKpV2ro9Xo/D0UrSpUwjP4E/TelOL/bscVjby/rK25Xa71SJl +pz/+0WatC7xrmYbvP33zGDLKe8bjq2RGlfgmadlVg3sslgf/WSxEo8bl6ancoWOA +WiFeIc9TVPC6b4nbqKqVz4vjccweGyBECMB6tkD9xOQ14R0WHNC8K47Wcdk= +-----END CERTIFICATE----- + +# Issuer: CN=AddTrust External CA Root O=AddTrust AB OU=AddTrust External TTP Network +# Subject: CN=AddTrust External CA Root O=AddTrust AB OU=AddTrust External TTP Network +# Label: "AddTrust External Root" +# Serial: 1 +# MD5 Fingerprint: 1d:35:54:04:85:78:b0:3f:42:42:4d:bf:20:73:0a:3f +# SHA1 Fingerprint: 02:fa:f3:e2:91:43:54:68:60:78:57:69:4d:f5:e4:5b:68:85:18:68 +# SHA256 Fingerprint: 68:7f:a4:51:38:22:78:ff:f0:c8:b1:1f:8d:43:d5:76:67:1c:6e:b2:bc:ea:b4:13:fb:83:d9:65:d0:6d:2f:f2 +-----BEGIN CERTIFICATE----- +MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU +MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs +IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290 +MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux +FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h +bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v +dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt +H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9 +uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX +mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX +a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN +E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0 +WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD +VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0 +Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU +cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx +IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN +AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH +YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5 +6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC +Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX +c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a +mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ= +-----END CERTIFICATE----- + +# Issuer: CN=AddTrust Public CA Root O=AddTrust AB OU=AddTrust TTP Network +# Subject: CN=AddTrust Public CA Root O=AddTrust AB OU=AddTrust TTP Network +# Label: "AddTrust Public Services Root" +# Serial: 1 +# MD5 Fingerprint: c1:62:3e:23:c5:82:73:9c:03:59:4b:2b:e9:77:49:7f +# SHA1 Fingerprint: 2a:b6:28:48:5e:78:fb:f3:ad:9e:79:10:dd:6b:df:99:72:2c:96:e5 +# SHA256 Fingerprint: 07:91:ca:07:49:b2:07:82:aa:d3:c7:d7:bd:0c:df:c9:48:58:35:84:3e:b2:d7:99:60:09:ce:43:ab:6c:69:27 +-----BEGIN CERTIFICATE----- +MIIEFTCCAv2gAwIBAgIBATANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJTRTEU +MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3 +b3JrMSAwHgYDVQQDExdBZGRUcnVzdCBQdWJsaWMgQ0EgUm9vdDAeFw0wMDA1MzAx +MDQxNTBaFw0yMDA1MzAxMDQxNTBaMGQxCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtB +ZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5ldHdvcmsxIDAeBgNV +BAMTF0FkZFRydXN0IFB1YmxpYyBDQSBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEA6Rowj4OIFMEg2Dybjxt+A3S72mnTRqX4jsIMEZBRpS9mVEBV +6tsfSlbunyNu9DnLoblv8n75XYcmYZ4c+OLspoH4IcUkzBEMP9smcnrHAZcHF/nX +GCwwfQ56HmIexkvA/X1id9NEHif2P0tEs7c42TkfYNVRknMDtABp4/MUTu7R3AnP +dzRGULD4EfL+OHn3Bzn+UZKXC1sIXzSGAa2Il+tmzV7R/9x98oTaunet3IAIx6eH +1lWfl2royBFkuucZKT8Rs3iQhCBSWxHveNCD9tVIkNAwHM+A+WD+eeSI8t0A65RF +62WUaUC6wNW0uLp9BBGo6zEFlpROWCGOn9Bg/QIDAQABo4HRMIHOMB0GA1UdDgQW +BBSBPjfYkrAfd59ctKtzquf2NGAv+jALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/BAUw +AwEB/zCBjgYDVR0jBIGGMIGDgBSBPjfYkrAfd59ctKtzquf2NGAv+qFopGYwZDEL +MAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQLExRBZGRU +cnVzdCBUVFAgTmV0d29yazEgMB4GA1UEAxMXQWRkVHJ1c3QgUHVibGljIENBIFJv +b3SCAQEwDQYJKoZIhvcNAQEFBQADggEBAAP3FUr4JNojVhaTdt02KLmuG7jD8WS6 +IBh4lSknVwW8fCr0uVFV2ocC3g8WFzH4qnkuCRO7r7IgGRLlk/lL+YPoRNWyQSW/ +iHVv/xD8SlTQX/D67zZzfRs2RcYhbbQVuE7PnFylPVoAjgbjPGsye/Kf8Lb93/Ao +GEjwxrzQvzSAlsJKsW2Ox5BF3i9nrEUEo3rcVZLJR2bYGozH7ZxOmuASu7VqTITh +4SINhwBk/ox9Yjllpu9CtoAlEmEBqCQTcAARJl/6NVDFSMwGR+gn2HCNX2TmoUQm +XiLsks3/QppEIW1cxeMiHV9HEufOX1362KqxMy3ZdvJOOjMMK7MtkAY= +-----END CERTIFICATE----- + +# Issuer: CN=AddTrust Qualified CA Root O=AddTrust AB OU=AddTrust TTP Network +# Subject: CN=AddTrust Qualified CA Root O=AddTrust AB OU=AddTrust TTP Network +# Label: "AddTrust Qualified Certificates Root" +# Serial: 1 +# MD5 Fingerprint: 27:ec:39:47:cd:da:5a:af:e2:9a:01:65:21:a9:4c:bb +# SHA1 Fingerprint: 4d:23:78:ec:91:95:39:b5:00:7f:75:8f:03:3b:21:1e:c5:4d:8b:cf +# SHA256 Fingerprint: 80:95:21:08:05:db:4b:bc:35:5e:44:28:d8:fd:6e:c2:cd:e3:ab:5f:b9:7a:99:42:98:8e:b8:f4:dc:d0:60:16 +-----BEGIN CERTIFICATE----- +MIIEHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJTRTEU +MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3 +b3JrMSMwIQYDVQQDExpBZGRUcnVzdCBRdWFsaWZpZWQgQ0EgUm9vdDAeFw0wMDA1 +MzAxMDQ0NTBaFw0yMDA1MzAxMDQ0NTBaMGcxCzAJBgNVBAYTAlNFMRQwEgYDVQQK +EwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5ldHdvcmsxIzAh +BgNVBAMTGkFkZFRydXN0IFF1YWxpZmllZCBDQSBSb290MIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEA5B6a/twJWoekn0e+EV+vhDTbYjx5eLfpMLXsDBwq +xBb/4Oxx64r1EW7tTw2R0hIYLUkVAcKkIhPHEWT/IhKauY5cLwjPcWqzZwFZ8V1G +87B4pfYOQnrjfxvM0PC3KP0q6p6zsLkEqv32x7SxuCqg+1jxGaBvcCV+PmlKfw8i +2O+tCBGaKZnhqkRFmhJePp1tUvznoD1oL/BLcHwTOK28FSXx1s6rosAx1i+f4P8U +WfyEk9mHfExUE+uf0S0R+Bg6Ot4l2ffTQO2kBhLEO+GRwVY18BTcZTYJbqukB8c1 +0cIDMzZbdSZtQvESa0NvS3GU+jQd7RNuyoB/mC9suWXY6QIDAQABo4HUMIHRMB0G +A1UdDgQWBBQ5lYtii1zJ1IC6WA+XPxUIQ8yYpzALBgNVHQ8EBAMCAQYwDwYDVR0T +AQH/BAUwAwEB/zCBkQYDVR0jBIGJMIGGgBQ5lYtii1zJ1IC6WA+XPxUIQ8yYp6Fr +pGkwZzELMAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQL +ExRBZGRUcnVzdCBUVFAgTmV0d29yazEjMCEGA1UEAxMaQWRkVHJ1c3QgUXVhbGlm +aWVkIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBABmrder4i2VhlRO6aQTv +hsoToMeqT2QbPxj2qC0sVY8FtzDqQmodwCVRLae/DLPt7wh/bDxGGuoYQ992zPlm +hpwsaPXpF/gxsxjE1kh9I0xowX67ARRvxdlu3rsEQmr49lx95dr6h+sNNVJn0J6X +dgWTP5XHAeZpVTh/EGGZyeNfpso+gmNIquIISD6q8rKFYqa0p9m9N5xotS1WfbC3 +P6CxB9bpT9zeRXEwMn8bLgn5v1Kh7sKAPgZcLlVAwRv1cEWw3F369nJad9Jjzc9Y +iQBCYz95OdBEsIJuQRno3eDBiFrRHnGTHyQwdOUeqN48Jzd/g66ed8/wMLH/S5no +xqE= +-----END CERTIFICATE----- + +# 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=GeoTrust Global CA O=GeoTrust Inc. +# Subject: CN=GeoTrust Global CA O=GeoTrust Inc. +# Label: "GeoTrust Global CA" +# Serial: 144470 +# MD5 Fingerprint: f7:75:ab:29:fb:51:4e:b7:77:5e:ff:05:3c:99:8e:f5 +# SHA1 Fingerprint: de:28:f4:a4:ff:e5:b9:2f:a3:c5:03:d1:a3:49:a7:f9:96:2a:82:12 +# SHA256 Fingerprint: ff:85:6a:2d:25:1d:cd:88:d3:66:56:f4:50:12:67:98:cf:ab:aa:de:40:79:9c:72:2d:e4:d2:b5:db:36:a7:3a +-----BEGIN CERTIFICATE----- +MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT +MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i +YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG +EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg +R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9 +9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq +fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv +iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU +1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+ +bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW +MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA +ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l +uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn +Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS +tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF +PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un +hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV +5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw== +-----END CERTIFICATE----- + +# Issuer: CN=GeoTrust Global CA 2 O=GeoTrust Inc. +# Subject: CN=GeoTrust Global CA 2 O=GeoTrust Inc. +# Label: "GeoTrust Global CA 2" +# Serial: 1 +# MD5 Fingerprint: 0e:40:a7:6c:de:03:5d:8f:d1:0f:e4:d1:8d:f9:6c:a9 +# SHA1 Fingerprint: a9:e9:78:08:14:37:58:88:f2:05:19:b0:6d:2b:0d:2b:60:16:90:7d +# SHA256 Fingerprint: ca:2d:82:a0:86:77:07:2f:8a:b6:76:4f:f0:35:67:6c:fe:3e:5e:32:5e:01:21:72:df:3f:92:09:6d:b7:9b:85 +-----BEGIN CERTIFICATE----- +MIIDZjCCAk6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBEMQswCQYDVQQGEwJVUzEW +MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFs +IENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMTkwMzA0MDUwMDAwWjBEMQswCQYDVQQG +EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3Qg +R2xvYmFsIENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDvPE1A +PRDfO1MA4Wf+lGAVPoWI8YkNkMgoI5kF6CsgncbzYEbYwbLVjDHZ3CB5JIG/NTL8 +Y2nbsSpr7iFY8gjpeMtvy/wWUsiRxP89c96xPqfCfWbB9X5SJBri1WeR0IIQ13hL +TytCOb1kLUCgsBDTOEhGiKEMuzozKmKY+wCdE1l/bztyqu6mD4b5BWHqZ38MN5aL +5mkWRxHCJ1kDs6ZgwiFAVvqgx306E+PsV8ez1q6diYD3Aecs9pYrEw15LNnA5IZ7 +S4wMcoKK+xfNAGw6EzywhIdLFnopsk/bHdQL82Y3vdj2V7teJHq4PIu5+pIaGoSe +2HSPqht/XvT+RSIhAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE +FHE4NvICMVNHK266ZUapEBVYIAUJMB8GA1UdIwQYMBaAFHE4NvICMVNHK266ZUap +EBVYIAUJMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQUFAAOCAQEAA/e1K6td +EPx7srJerJsOflN4WT5CBP51o62sgU7XAotexC3IUnbHLB/8gTKY0UvGkpMzNTEv +/NgdRN3ggX+d6YvhZJFiCzkIjKx0nVnZellSlxG5FntvRdOW2TF9AjYPnDtuzywN +A0ZF66D0f0hExghAzN4bcLUprbqLOzRldRtxIR0sFAqwlpW41uryZfspuk/qkZN0 +abby/+Ea0AzRdoXLiiW9l14sbxWZJue2Kf8i7MkCx1YAzUm5s2x7UwQa4qjJqhIF +I8LO57sEAszAR6LkxCkvW0VXiVHuPOtSCP8HNR6fNWpHSlaY0VqFH4z1Ir+rzoPz +4iIprn2DQKi6bA== +-----END CERTIFICATE----- + +# Issuer: CN=GeoTrust Universal CA O=GeoTrust Inc. +# Subject: CN=GeoTrust Universal CA O=GeoTrust Inc. +# Label: "GeoTrust Universal CA" +# Serial: 1 +# MD5 Fingerprint: 92:65:58:8b:a2:1a:31:72:73:68:5c:b4:a5:7a:07:48 +# SHA1 Fingerprint: e6:21:f3:35:43:79:05:9a:4b:68:30:9d:8a:2f:74:22:15:87:ec:79 +# SHA256 Fingerprint: a0:45:9b:9f:63:b2:25:59:f5:fa:5d:4c:6d:b3:f9:f7:2f:f1:93:42:03:35:78:f0:73:bf:1d:1b:46:cb:b9:12 +-----BEGIN CERTIFICATE----- +MIIFaDCCA1CgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJVUzEW +MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgVW5pdmVy +c2FsIENBMB4XDTA0MDMwNDA1MDAwMFoXDTI5MDMwNDA1MDAwMFowRTELMAkGA1UE +BhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xHjAcBgNVBAMTFUdlb1RydXN0 +IFVuaXZlcnNhbCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKYV +VaCjxuAfjJ0hUNfBvitbtaSeodlyWL0AG0y/YckUHUWCq8YdgNY96xCcOq9tJPi8 +cQGeBvV8Xx7BDlXKg5pZMK4ZyzBIle0iN430SppyZj6tlcDgFgDgEB8rMQ7XlFTT +QjOgNB0eRXbdT8oYN+yFFXoZCPzVx5zw8qkuEKmS5j1YPakWaDwvdSEYfyh3peFh +F7em6fgemdtzbvQKoiFs7tqqhZJmr/Z6a4LauiIINQ/PQvE1+mrufislzDoR5G2v +c7J2Ha3QsnhnGqQ5HFELZ1aD/ThdDc7d8Lsrlh/eezJS/R27tQahsiFepdaVaH/w +mZ7cRQg+59IJDTWU3YBOU5fXtQlEIGQWFwMCTFMNaN7VqnJNk22CDtucvc+081xd +VHppCZbW2xHBjXWotM85yM48vCR85mLK4b19p71XZQvk/iXttmkQ3CgaRr0BHdCX +teGYO8A3ZNY9lO4L4fUorgtWv3GLIylBjobFS1J72HGrH4oVpjuDWtdYAVHGTEHZ +f9hBZ3KiKN9gg6meyHv8U3NyWfWTehd2Ds735VzZC1U0oqpbtWpU5xPKV+yXbfRe +Bi9Fi1jUIxaS5BZuKGNZMN9QAZxjiRqf2xeUgnA3wySemkfWWspOqGmJch+RbNt+ +nhutxx9z3SxPGWX9f5NAEC7S8O08ni4oPmkmM8V7AgMBAAGjYzBhMA8GA1UdEwEB +/wQFMAMBAf8wHQYDVR0OBBYEFNq7LqqwDLiIJlF0XG0D08DYj3rWMB8GA1UdIwQY +MBaAFNq7LqqwDLiIJlF0XG0D08DYj3rWMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG +9w0BAQUFAAOCAgEAMXjmx7XfuJRAyXHEqDXsRh3ChfMoWIawC/yOsjmPRFWrZIRc +aanQmjg8+uUfNeVE44B5lGiku8SfPeE0zTBGi1QrlaXv9z+ZhP015s8xxtxqv6fX +IwjhmF7DWgh2qaavdy+3YL1ERmrvl/9zlcGO6JP7/TG37FcREUWbMPEaiDnBTzyn +ANXH/KttgCJwpQzgXQQpAvvLoJHRfNbDflDVnVi+QTjruXU8FdmbyUqDWcDaU/0z +uzYYm4UPFd3uLax2k7nZAY1IEKj79TiG8dsKxr2EoyNB3tZ3b4XUhRxQ4K5RirqN +Pnbiucon8l+f725ZDQbYKxek0nxru18UGkiPGkzns0ccjkxFKyDuSN/n3QmOGKja +QI2SJhFTYXNd673nxE0pN2HrrDktZy4W1vUAg4WhzH92xH3kt0tm7wNFYGm2DFKW +koRepqO1pD4r2czYG0eq8kTaT/kD6PAUyz/zg97QwVTjt+gKN02LIFkDMBmhLMi9 +ER/frslKxfMnZmaGrGiR/9nmUxwPi1xpZQomyB40w11Re9epnAahNt3ViZS82eQt +DF4JbAiXfKM9fJP/P6EUp8+1Xevb2xzEdt+Iub1FBZUbrvxGakyvSOPOrg/Sfuvm +bJxPgWp6ZKy7PtXny3YuxadIwVyQD8vIP/rmMuGNG2+k5o7Y+SlIis5z/iw= +-----END CERTIFICATE----- + +# Issuer: CN=GeoTrust Universal CA 2 O=GeoTrust Inc. +# Subject: CN=GeoTrust Universal CA 2 O=GeoTrust Inc. +# Label: "GeoTrust Universal CA 2" +# Serial: 1 +# MD5 Fingerprint: 34:fc:b8:d0:36:db:9e:14:b3:c2:f2:db:8f:e4:94:c7 +# SHA1 Fingerprint: 37:9a:19:7b:41:85:45:35:0c:a6:03:69:f3:3c:2e:af:47:4f:20:79 +# SHA256 Fingerprint: a0:23:4f:3b:c8:52:7c:a5:62:8e:ec:81:ad:5d:69:89:5d:a5:68:0d:c9:1d:1c:b8:47:7f:33:f8:78:b9:5b:0b +-----BEGIN CERTIFICATE----- +MIIFbDCCA1SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBHMQswCQYDVQQGEwJVUzEW +MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVy +c2FsIENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMjkwMzA0MDUwMDAwWjBHMQswCQYD +VQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1 +c3QgVW5pdmVyc2FsIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC +AQCzVFLByT7y2dyxUxpZKeexw0Uo5dfR7cXFS6GqdHtXr0om/Nj1XqduGdt0DE81 +WzILAePb63p3NeqqWuDW6KFXlPCQo3RWlEQwAx5cTiuFJnSCegx2oG9NzkEtoBUG +FF+3Qs17j1hhNNwqCPkuwwGmIkQcTAeC5lvO0Ep8BNMZcyfwqph/Lq9O64ceJHdq +XbboW0W63MOhBW9Wjo8QJqVJwy7XQYci4E+GymC16qFjwAGXEHm9ADwSbSsVsaxL +se4YuU6W3Nx2/zu+z18DwPw76L5GG//aQMJS9/7jOvdqdzXQ2o3rXhhqMcceujwb +KNZrVMaqW9eiLBsZzKIC9ptZvTdrhrVtgrrY6slWvKk2WP0+GfPtDCapkzj4T8Fd +IgbQl+rhrcZV4IErKIM6+vR7IVEAvlI4zs1meaj0gVbi0IMJR1FbUGrP20gaXT73 +y/Zl92zxlfgCOzJWgjl6W70viRu/obTo/3+NjN8D8WBOWBFM66M/ECuDmgFz2ZRt +hAAnZqzwcEAJQpKtT5MNYQlRJNiS1QuUYbKHsu3/mjX/hVTK7URDrBs8FmtISgoc +QIgfksILAAX/8sgCSqSqqcyZlpwvWOB94b67B9xfBHJcMTTD7F8t4D1kkCLm0ey4 +Lt1ZrtmhN79UNdxzMk+MBB4zsslG8dhcyFVQyWi9qLo2CQIDAQABo2MwYTAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAfBgNV +HSMEGDAWgBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAOBgNVHQ8BAf8EBAMCAYYwDQYJ +KoZIhvcNAQEFBQADggIBAGbBxiPz2eAubl/oz66wsCVNK/g7WJtAJDday6sWSf+z +dXkzoS9tcBc0kf5nfo/sm+VegqlVHy/c1FEHEv6sFj4sNcZj/NwQ6w2jqtB8zNHQ +L1EuxBRa3ugZ4T7GzKQp5y6EqgYweHZUcyiYWTjgAA1i00J9IZ+uPTqM1fp3DRgr +Fg5fNuH8KrUwJM/gYwx7WBr+mbpCErGR9Hxo4sjoryzqyX6uuyo9DRXcNJW2GHSo +ag/HtPQTxORb7QrSpJdMKu0vbBKJPfEncKpqA1Ihn0CoZ1Dy81of398j9tx4TuaY +T1U6U+Pv8vSfx3zYWK8pIpe44L2RLrB27FcRz+8pRPPphXpgY+RdM4kX2TGq2tbz +GDVyz4crL2MjhF2EjD9XoIj8mZEoJmmZ1I+XRL6O1UixpCgp8RW04eWe3fiPpm8m +1wk8OhwRDqZsN/etRIcsKMfYdIKz0G9KV7s1KSegi+ghp4dkNl3M2Basx7InQJJV +OCiNUW7dFGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH +6aLcr34YEoP9VhdBLtUpgn2Z9DH2canPLAEnpQW5qrJITirvn5NSUZU8UnOOVkwX +QMAJKOSLakhT2+zNVVXxxvjpoixMptEmX36vWkzaH6byHCx+rgIW0lbQL1dTR+iS +-----END CERTIFICATE----- + +# Issuer: CN=America Online Root Certification Authority 1 O=America Online Inc. +# Subject: CN=America Online Root Certification Authority 1 O=America Online Inc. +# Label: "America Online Root Certification Authority 1" +# Serial: 1 +# MD5 Fingerprint: 14:f1:08:ad:9d:fa:64:e2:89:e7:1c:cf:a8:ad:7d:5e +# SHA1 Fingerprint: 39:21:c1:15:c1:5d:0e:ca:5c:cb:5b:c4:f0:7d:21:d8:05:0b:56:6a +# SHA256 Fingerprint: 77:40:73:12:c6:3a:15:3d:5b:c0:0b:4e:51:75:9c:df:da:c2:37:dc:2a:33:b6:79:46:e9:8e:9b:fa:68:0a:e3 +-----BEGIN CERTIFICATE----- +MIIDpDCCAoygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEc +MBoGA1UEChMTQW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBP +bmxpbmUgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAxMB4XDTAyMDUyODA2 +MDAwMFoXDTM3MTExOTIwNDMwMFowYzELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0Ft +ZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2EgT25saW5lIFJvb3Qg +Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMTCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAKgv6KRpBgNHw+kqmP8ZonCaxlCyfqXfaE0bfA+2l2h9LaaLl+lk +hsmj76CGv2BlnEtUiMJIxUo5vxTjWVXlGbR0yLQFOVwWpeKVBeASrlmLojNoWBym +1BW32J/X3HGrfpq/m44zDyL9Hy7nBzbvYjnF3cu6JRQj3gzGPTzOggjmZj7aUTsW +OqMFf6Dch9Wc/HKpoH145LcxVR5lu9RhsCFg7RAycsWSJR74kEoYeEfffjA3PlAb +2xzTa5qGUwew76wGePiEmf4hjUyAtgyC9mZweRrTT6PP8c9GsEsPPt2IYriMqQko +O3rHl+Ee5fSfwMCuJKDIodkP1nsmgmkyPacCAwEAAaNjMGEwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUAK3Zo/Z59m50qX8zPYEX10zPM94wHwYDVR0jBBgwFoAU +AK3Zo/Z59m50qX8zPYEX10zPM94wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB +BQUAA4IBAQB8itEfGDeC4Liwo+1WlchiYZwFos3CYiZhzRAW18y0ZTTQEYqtqKkF +Zu90821fnZmv9ov761KyBZiibyrFVL0lvV+uyIbqRizBs73B6UlwGBaXCBOMIOAb +LjpHyx7kADCVW/RFo8AasAFOq73AI25jP4BKxQft3OJvx8Fi8eNy1gTIdGcL+oir +oQHIb/AUr9KZzVGTfu0uOMe9zkZQPXLjeSWdm4grECDdpbgyn43gKd8hdIaC2y+C +MMbHNYaz+ZZfRtsMRf3zUMNvxsNIrUam4SdHCh0Om7bCd39j8uB9Gr784N/Xx6ds +sPmuujz9dLQR6FgNgLzTqIA6me11zEZ7 +-----END CERTIFICATE----- + +# Issuer: CN=America Online Root Certification Authority 2 O=America Online Inc. +# Subject: CN=America Online Root Certification Authority 2 O=America Online Inc. +# Label: "America Online Root Certification Authority 2" +# Serial: 1 +# MD5 Fingerprint: d6:ed:3c:ca:e2:66:0f:af:10:43:0d:77:9b:04:09:bf +# SHA1 Fingerprint: 85:b5:ff:67:9b:0c:79:96:1f:c8:6e:44:22:00:46:13:db:17:92:84 +# SHA256 Fingerprint: 7d:3b:46:5a:60:14:e5:26:c0:af:fc:ee:21:27:d2:31:17:27:ad:81:1c:26:84:2d:00:6a:f3:73:06:cc:80:bd +-----BEGIN CERTIFICATE----- +MIIFpDCCA4ygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEc +MBoGA1UEChMTQW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBP +bmxpbmUgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAyMB4XDTAyMDUyODA2 +MDAwMFoXDTM3MDkyOTE0MDgwMFowYzELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0Ft +ZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2EgT25saW5lIFJvb3Qg +Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBAMxBRR3pPU0Q9oyxQcngXssNt79Hc9PwVU3dxgz6sWYFas14tNwC +206B89enfHG8dWOgXeMHDEjsJcQDIPT/DjsS/5uN4cbVG7RtIuOx238hZK+GvFci +KtZHgVdEglZTvYYUAQv8f3SkWq7xuhG1m1hagLQ3eAkzfDJHA1zEpYNI9FdWboE2 +JxhP7JsowtS013wMPgwr38oE18aO6lhOqKSlGBxsRZijQdEt0sdtjRnxrXm3gT+9 +BoInLRBYBbV4Bbkv2wxrkJB+FFk4u5QkE+XRnRTf04JNRvCAOVIyD+OEsnpD8l7e +Xz8d3eOyG6ChKiMDbi4BFYdcpnV1x5dhvt6G3NRI270qv0pV2uh9UPu0gBe4lL8B +PeraunzgWGcXuVjgiIZGZ2ydEEdYMtA1fHkqkKJaEBEjNa0vzORKW6fIJ/KD3l67 +Xnfn6KVuY8INXWHQjNJsWiEOyiijzirplcdIz5ZvHZIlyMbGwcEMBawmxNJ10uEq +Z8A9W6Wa6897GqidFEXlD6CaZd4vKL3Ob5Rmg0gp2OpljK+T2WSfVVcmv2/LNzGZ +o2C7HK2JNDJiuEMhBnIMoVxtRsX6Kc8w3onccVvdtjc+31D1uAclJuW8tf48ArO3 ++L5DwYcRlJ4jbBeKuIonDFRH8KmzwICMoCfrHRnjB453cMor9H124HhnAgMBAAGj +YzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFE1FwWg4u3OpaaEg5+31IqEj +FNeeMB8GA1UdIwQYMBaAFE1FwWg4u3OpaaEg5+31IqEjFNeeMA4GA1UdDwEB/wQE +AwIBhjANBgkqhkiG9w0BAQUFAAOCAgEAZ2sGuV9FOypLM7PmG2tZTiLMubekJcmn +xPBUlgtk87FYT15R/LKXeydlwuXK5w0MJXti4/qftIe3RUavg6WXSIylvfEWK5t2 +LHo1YGwRgJfMqZJS5ivmae2p+DYtLHe/YUjRYwu5W1LtGLBDQiKmsXeu3mnFzccc +obGlHBD7GL4acN3Bkku+KVqdPzW+5X1R+FXgJXUjhx5c3LqdsKyzadsXg8n33gy8 +CNyRnqjQ1xU3c6U1uPx+xURABsPr+CKAXEfOAuMRn0T//ZoyzH1kUQ7rVyZ2OuMe +IjzCpjbdGe+n/BLzJsBZMYVMnNjP36TMzCmT/5RtdlwTCJfy7aULTd3oyWgOZtMA +DjMSW7yV5TKQqLPGbIOtd+6Lfn6xqavT4fG2wLHqiMDn05DpKJKUe2h7lyoKZy2F +AjgQ5ANh1NolNscIWC2hp1GvMApJ9aZphwctREZ2jirlmjvXGKL8nDgQzMY70rUX +Om/9riW99XJZZLF0KjhfGEzfz3EEWjbUvy+ZnOjZurGV5gJLIaFb1cFPj65pbVPb +AZO1XB4Y3WRayhgoPmMEEf0cjQAPuDffZ4qdZqkCapH/E8ovXYO8h5Ns3CRRFgQl +Zvqz2cK6Kb6aSDiCmfS/O0oxGfm/jiEzFMpPVF/7zvuPcX/9XhmgD0uRuMRUvAaw +RY8mkaKO/qk= +-----END CERTIFICATE----- + +# Issuer: CN=AAA Certificate Services O=Comodo CA Limited +# Subject: CN=AAA Certificate Services O=Comodo CA Limited +# Label: "Comodo AAA Services root" +# Serial: 1 +# MD5 Fingerprint: 49:79:04:b0:eb:87:19:ac:47:b0:bc:11:51:9b:74:d0 +# SHA1 Fingerprint: d1:eb:23:a4:6d:17:d6:8f:d9:25:64:c2:f1:f1:60:17:64:d8:e3:49 +# SHA256 Fingerprint: d7:a7:a0:fb:5d:7e:27:31:d7:71:e9:48:4e:bc:de:f7:1d:5f:0c:3e:0a:29:48:78:2b:c8:3e:e0:ea:69:9e:f4 +-----BEGIN CERTIFICATE----- +MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb +MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow +GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj +YXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL +MAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE +BwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM +GEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua +BtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe +3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4 +YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR +rOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm +ez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU +oBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF +MAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v +QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t +b2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF +AAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q +GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz +Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2 +G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi +l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3 +smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== +-----END CERTIFICATE----- + +# Issuer: CN=Secure Certificate Services O=Comodo CA Limited +# Subject: CN=Secure Certificate Services O=Comodo CA Limited +# Label: "Comodo Secure Services root" +# Serial: 1 +# MD5 Fingerprint: d3:d9:bd:ae:9f:ac:67:24:b3:c8:1b:52:e1:b9:a9:bd +# SHA1 Fingerprint: 4a:65:d5:f4:1d:ef:39:b8:b8:90:4a:4a:d3:64:81:33:cf:c7:a1:d1 +# SHA256 Fingerprint: bd:81:ce:3b:4f:65:91:d1:1a:67:b5:fc:7a:47:fd:ef:25:52:1b:f9:aa:4e:18:b9:e3:df:2e:34:a7:80:3b:e8 +-----BEGIN CERTIFICATE----- +MIIEPzCCAyegAwIBAgIBATANBgkqhkiG9w0BAQUFADB+MQswCQYDVQQGEwJHQjEb +MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow +GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEkMCIGA1UEAwwbU2VjdXJlIENlcnRp +ZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVow +fjELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxJDAiBgNV +BAMMG1NlY3VyZSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBAMBxM4KK0HDrc4eCQNUd5MvJDkKQ+d40uaG6EfQlhfPM +cm3ye5drswfxdySRXyWP9nQ95IDC+DwN879A6vfIUtFyb+/Iq0G4bi4XKpVpDM3S +HpR7LZQdqnXXs5jLrLxkU0C8j6ysNstcrbvd4JQX7NFc0L/vpZXJkMWwrPsbQ996 +CF23uPJAGysnnlDOXmWCiIxe004MeuoIkbY2qitC++rCoznl2yY4rYsK7hljxxwk +3wN42ubqwUcaCwtGCd0C/N7Lh1/XMGNooa7cMqG6vv5Eq2i2pRcV/b3Vp6ea5EQz +6YiO/O1R65NxTq0B50SOqy3LqP4BSUjwwN3HaNiS/j0CAwEAAaOBxzCBxDAdBgNV +HQ4EFgQUPNiTiMLAggnMAZkGkyDpnnAJY08wDgYDVR0PAQH/BAQDAgEGMA8GA1Ud +EwEB/wQFMAMBAf8wgYEGA1UdHwR6MHgwO6A5oDeGNWh0dHA6Ly9jcmwuY29tb2Rv +Y2EuY29tL1NlY3VyZUNlcnRpZmljYXRlU2VydmljZXMuY3JsMDmgN6A1hjNodHRw +Oi8vY3JsLmNvbW9kby5uZXQvU2VjdXJlQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmww +DQYJKoZIhvcNAQEFBQADggEBAIcBbSMdflsXfcFhMs+P5/OKlFlm4J4oqF7Tt/Q0 +5qo5spcWxYJvMqTpjOev/e/C6LlLqqP05tqNZSH7uoDrJiiFGv45jN5bBAS0VPmj +Z55B+glSzAVIqMk/IQQezkhr/IXownuvf7fM+F86/TXGDe+X3EyrEeFryzHRbPtI +gKvcnDe4IRRLDXE97IMzbtFuMhbsmMcWi1mmNKsFVy2T96oTy9IT4rcuO81rUBcJ +aD61JlfutuC23bkpgHl9j6PwpCikFcSF9CfUa7/lXORlAnZUtOM3ZiTTGWHIUhDl +izeauan5Hb/qmZJhlv8BzaFfDbxxvA6sCx1HRR3B7Hzs/Sk= +-----END CERTIFICATE----- + +# Issuer: CN=Trusted Certificate Services O=Comodo CA Limited +# Subject: CN=Trusted Certificate Services O=Comodo CA Limited +# Label: "Comodo Trusted Services root" +# Serial: 1 +# MD5 Fingerprint: 91:1b:3f:6e:cd:9e:ab:ee:07:fe:1f:71:d2:b3:61:27 +# SHA1 Fingerprint: e1:9f:e3:0e:8b:84:60:9e:80:9b:17:0d:72:a8:c5:ba:6e:14:09:bd +# SHA256 Fingerprint: 3f:06:e5:56:81:d4:96:f5:be:16:9e:b5:38:9f:9f:2b:8f:f6:1e:17:08:df:68:81:72:48:49:cd:5d:27:cb:69 +-----BEGIN CERTIFICATE----- +MIIEQzCCAyugAwIBAgIBATANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJHQjEb +MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow +GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDElMCMGA1UEAwwcVHJ1c3RlZCBDZXJ0 +aWZpY2F0ZSBTZXJ2aWNlczAeFw0wNDAxMDEwMDAwMDBaFw0yODEyMzEyMzU5NTla +MH8xCzAJBgNVBAYTAkdCMRswGQYDVQQIDBJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO +BgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoMEUNvbW9kbyBDQSBMaW1pdGVkMSUwIwYD +VQQDDBxUcnVzdGVkIENlcnRpZmljYXRlIFNlcnZpY2VzMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEA33FvNlhTWvI2VFeAxHQIIO0Yfyod5jWaHiWsnOWW +fnJSoBVC21ndZHoa0Lh73TkVvFVIxO06AOoxEbrycXQaZ7jPM8yoMa+j49d/vzMt +TGo87IvDktJTdyR0nAducPy9C1t2ul/y/9c3S0pgePfw+spwtOpZqqPOSC+pw7IL +fhdyFgymBwwbOM/JYrc/oJOlh0Hyt3BAd9i+FHzjqMB6juljatEPmsbS9Is6FARW +1O24zG71++IsWL1/T2sr92AkWCTOJu80kTrV44HQsvAEAtdbtz6SrGsSivnkBbA7 +kUlcsutT6vifR4buv5XAwAaf0lteERv0xwQ1KdJVXOTt6wIDAQABo4HJMIHGMB0G +A1UdDgQWBBTFe1i97doladL3WRaoszLAeydb9DAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zCBgwYDVR0fBHwwejA8oDqgOIY2aHR0cDovL2NybC5jb21v +ZG9jYS5jb20vVHJ1c3RlZENlcnRpZmljYXRlU2VydmljZXMuY3JsMDqgOKA2hjRo +dHRwOi8vY3JsLmNvbW9kby5uZXQvVHJ1c3RlZENlcnRpZmljYXRlU2VydmljZXMu +Y3JsMA0GCSqGSIb3DQEBBQUAA4IBAQDIk4E7ibSvuIQSTI3S8NtwuleGFTQQuS9/ +HrCoiWChisJ3DFBKmwCL2Iv0QeLQg4pKHBQGsKNoBXAxMKdTmw7pSqBYaWcOrp32 +pSxBvzwGa+RZzG0Q8ZZvH9/0BAKkn0U+yNj6NkZEUD+Cl5EfKNsYEYwq5GWDVxIS +jBc/lDb+XbDABHcTuPQV1T84zJQ6VdCsmPW6AF/ghhmBeC8owH7TzEIK9a5QoNE+ +xqFx7D+gIIxmOom0jtTYsU0lR+4viMi14QVFwL4Ucd56/Y57fU0IlqUSc/Atyjcn +dBInTMu2l+nZrghtWjlA3QVHdWpaIbOjGM9O9y5Xt5hwXsjEeLBi +-----END CERTIFICATE----- + +# Issuer: CN=UTN - DATACorp SGC O=The USERTRUST Network OU=http://www.usertrust.com +# Subject: CN=UTN - DATACorp SGC O=The USERTRUST Network OU=http://www.usertrust.com +# Label: "UTN DATACorp SGC Root CA" +# Serial: 91374294542884689855167577680241077609 +# MD5 Fingerprint: b3:a5:3e:77:21:6d:ac:4a:c0:c9:fb:d5:41:3d:ca:06 +# SHA1 Fingerprint: 58:11:9f:0e:12:82:87:ea:50:fd:d9:87:45:6f:4f:78:dc:fa:d6:d4 +# SHA256 Fingerprint: 85:fb:2f:91:dd:12:27:5a:01:45:b6:36:53:4f:84:02:4a:d6:8b:69:b8:ee:88:68:4f:f7:11:37:58:05:b3:48 +-----BEGIN CERTIFICATE----- +MIIEXjCCA0agAwIBAgIQRL4Mi1AAIbQR0ypoBqmtaTANBgkqhkiG9w0BAQUFADCB +kzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug +Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho +dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xGzAZBgNVBAMTElVUTiAtIERBVEFDb3Jw +IFNHQzAeFw05OTA2MjQxODU3MjFaFw0xOTA2MjQxOTA2MzBaMIGTMQswCQYDVQQG +EwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYD +VQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cu +dXNlcnRydXN0LmNvbTEbMBkGA1UEAxMSVVROIC0gREFUQUNvcnAgU0dDMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3+5YEKIrblXEjr8uRgnn4AgPLit6 +E5Qbvfa2gI5lBZMAHryv4g+OGQ0SR+ysraP6LnD43m77VkIVni5c7yPeIbkFdicZ +D0/Ww5y0vpQZY/KmEQrrU0icvvIpOxboGqBMpsn0GFlowHDyUwDAXlCCpVZvNvlK +4ESGoE1O1kduSUrLZ9emxAW5jh70/P/N5zbgnAVssjMiFdC04MwXwLLA9P4yPykq +lXvY8qdOD1R8oQ2AswkDwf9c3V6aPryuvEeKaq5xyh+xKrhfQgUL7EYw0XILyulW +bfXv33i+Ybqypa4ETLyorGkVl73v67SMvzX41MPRKA5cOp9wGDMgd8SirwIDAQAB +o4GrMIGoMAsGA1UdDwQEAwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRT +MtGzz3/64PGgXYVOktKeRR20TzA9BgNVHR8ENjA0MDKgMKAuhixodHRwOi8vY3Js +LnVzZXJ0cnVzdC5jb20vVVROLURBVEFDb3JwU0dDLmNybDAqBgNVHSUEIzAhBggr +BgEFBQcDAQYKKwYBBAGCNwoDAwYJYIZIAYb4QgQBMA0GCSqGSIb3DQEBBQUAA4IB +AQAnNZcAiosovcYzMB4p/OL31ZjUQLtgyr+rFywJNn9Q+kHcrpY6CiM+iVnJowft +Gzet/Hy+UUla3joKVAgWRcKZsYfNjGjgaQPpxE6YsjuMFrMOoAyYUJuTqXAJyCyj +j98C5OBxOvG0I3KgqgHf35g+FFCgMSa9KOlaMCZ1+XtgHI3zzVAmbQQnmt/VDUVH +KWss5nbZqSl9Mt3JNjy9rjXxEZ4du5A/EkdOjtd+D2JzHVImOBwYSf0wdJrE5SIv +2MCN7ZF6TACPcn9d2t0bi0Vr591pl6jFVkwPDPafepE39peC4N1xaf92P2BNPM/3 +mfnGV/TJVTl4uix5yaaIK/QI +-----END CERTIFICATE----- + +# Issuer: CN=UTN-USERFirst-Hardware O=The USERTRUST Network OU=http://www.usertrust.com +# Subject: CN=UTN-USERFirst-Hardware O=The USERTRUST Network OU=http://www.usertrust.com +# Label: "UTN USERFirst Hardware Root CA" +# Serial: 91374294542884704022267039221184531197 +# MD5 Fingerprint: 4c:56:41:e5:0d:bb:2b:e8:ca:a3:ed:18:08:ad:43:39 +# SHA1 Fingerprint: 04:83:ed:33:99:ac:36:08:05:87:22:ed:bc:5e:46:00:e3:be:f9:d7 +# SHA256 Fingerprint: 6e:a5:47:41:d0:04:66:7e:ed:1b:48:16:63:4a:a3:a7:9e:6e:4b:96:95:0f:82:79:da:fc:8d:9b:d8:81:21:37 +-----BEGIN CERTIFICATE----- +MIIEdDCCA1ygAwIBAgIQRL4Mi1AAJLQR0zYq/mUK/TANBgkqhkiG9w0BAQUFADCB +lzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug +Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho +dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3Qt +SGFyZHdhcmUwHhcNOTkwNzA5MTgxMDQyWhcNMTkwNzA5MTgxOTIyWjCBlzELMAkG +A1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEe +MBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8v +d3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdh +cmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCx98M4P7Sof885glFn +0G2f0v9Y8+efK+wNiVSZuTiZFvfgIXlIwrthdBKWHTxqctU8EGc6Oe0rE81m65UJ +M6Rsl7HoxuzBdXmcRl6Nq9Bq/bkqVRcQVLMZ8Jr28bFdtqdt++BxF2uiiPsA3/4a +MXcMmgF6sTLjKwEHOG7DpV4jvEWbe1DByTCP2+UretNb+zNAHqDVmBe8i4fDidNd +oI6yqqr2jmmIBsX6iSHzCJ1pLgkzmykNRg+MzEk0sGlRvfkGzWitZky8PqxhvQqI +DsjfPe58BEydCl5rkdbux+0ojatNh4lz0G6k0B4WixThdkQDf2Os5M1JnMWS9Ksy +oUhbAgMBAAGjgbkwgbYwCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFKFyXyYbKJhDlV0HN9WFlp1L0sNFMEQGA1UdHwQ9MDswOaA3oDWGM2h0 +dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNFUkZpcnN0LUhhcmR3YXJlLmNy +bDAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwUGCCsGAQUFBwMGBggrBgEF +BQcDBzANBgkqhkiG9w0BAQUFAAOCAQEARxkP3nTGmZev/K0oXnWO6y1n7k57K9cM +//bey1WiCuFMVGWTYGufEpytXoMs61quwOQt9ABjHbjAbPLPSbtNk28Gpgoiskli +CE7/yMgUsogWXecB5BKV5UU0s4tpvc+0hY91UZ59Ojg6FEgSxvunOxqNDYJAB+gE +CJChicsZUN/KHAG8HQQZexB2lzvukJDKxA4fFm517zP4029bHpbj4HR3dHuKom4t +3XbWOTCC8KucUvIqx69JXn7HaOWCgchqJ/kniCrVWFCVH/A7HFe7fRQ5YiuayZSS +KqMiDP+JJn1fIytH1xUdqWqeUQ0qUZ6B+dQ7XnASfxAynB67nfhmqA== +-----END CERTIFICATE----- + +# Issuer: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com +# Subject: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com +# Label: "XRamp Global CA Root" +# Serial: 107108908803651509692980124233745014957 +# MD5 Fingerprint: a1:0b:44:b3:ca:10:d8:00:6e:9d:0f:d8:0f:92:0a:d1 +# SHA1 Fingerprint: b8:01:86:d1:eb:9c:86:a5:41:04:cf:30:54:f3:4c:52:b7:e5:58:c6 +# SHA256 Fingerprint: ce:cd:dc:90:50:99:d8:da:df:c5:b1:d2:09:b7:37:cb:e2:c1:8c:fb:2c:10:c0:ff:0b:cf:0d:32:86:fc:1a:a2 +-----BEGIN CERTIFICATE----- +MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCB +gjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEk +MCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRY +UmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQxMTAxMTcx +NDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3 +dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2Vy +dmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB +dXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS6 +38eMpSe2OAtp87ZOqCwuIR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCP +KZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMxfoArtYzAQDsRhtDLooY2YKTVMIJt2W7Q +DxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FEzG+gSqmUsE3a56k0enI4 +qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqsAxcZZPRa +JSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNVi +PvryxS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0P +BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASs +jVy16bYbMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0 +eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQEwDQYJKoZIhvcNAQEFBQAD +ggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc/Kh4ZzXxHfAR +vbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt +qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLa +IR9NmXmd4c8nnxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSy +i6mx5O+aGtA9aZnuqCij4Tyz8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQ +O+7ETPTsJ3xCwnR8gooJybQDJbw= +-----END CERTIFICATE----- + +# Issuer: O=The Go Daddy Group, Inc. OU=Go Daddy Class 2 Certification Authority +# Subject: O=The Go Daddy Group, Inc. OU=Go Daddy Class 2 Certification Authority +# Label: "Go Daddy Class 2 CA" +# Serial: 0 +# MD5 Fingerprint: 91:de:06:25:ab:da:fd:32:17:0c:bb:25:17:2a:84:67 +# SHA1 Fingerprint: 27:96:ba:e6:3f:18:01:e2:77:26:1b:a0:d7:77:70:02:8f:20:ee:e4 +# SHA256 Fingerprint: c3:84:6b:f2:4b:9e:93:ca:64:27:4c:0e:c6:7c:1e:cc:5e:02:4f:fc:ac:d2:d7:40:19:35:0e:81:fe:54:6a:e4 +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh +MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE +YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3 +MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo +ZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg +MiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN +ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA +PVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w +wdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi +EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY +avx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+ +YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE +sNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h +/t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5 +IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD +ggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy +OO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P +TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ +HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER +dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf +ReYNnyicsbkqWletNw+vHX/bvZ8= +-----END CERTIFICATE----- + +# Issuer: O=Starfield Technologies, Inc. OU=Starfield Class 2 Certification Authority +# Subject: O=Starfield Technologies, Inc. OU=Starfield Class 2 Certification Authority +# Label: "Starfield Class 2 CA" +# Serial: 0 +# MD5 Fingerprint: 32:4a:4b:bb:c8:63:69:9b:be:74:9a:c6:dd:1d:46:24 +# SHA1 Fingerprint: ad:7e:1c:28:b0:64:ef:8f:60:03:40:20:14:c3:d0:e3:37:0e:b5:8a +# SHA256 Fingerprint: 14:65:fa:20:53:97:b8:76:fa:a6:f0:a9:95:8e:55:90:e4:0f:cc:7f:aa:4f:b7:c2:c8:67:75:21:fb:5f:b6:58 +-----BEGIN CERTIFICATE----- +MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl +MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp +U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw +NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE +ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp +ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3 +DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf +8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN ++lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0 +X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa +K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA +1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G +A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR +zt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0 +YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD +bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w +DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3 +L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D +eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl +xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp +VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY +WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q= +-----END CERTIFICATE----- + +# Issuer: CN=StartCom Certification Authority O=StartCom Ltd. OU=Secure Digital Certificate Signing +# Subject: CN=StartCom Certification Authority O=StartCom Ltd. OU=Secure Digital Certificate Signing +# Label: "StartCom Certification Authority" +# Serial: 1 +# MD5 Fingerprint: 22:4d:8f:8a:fc:f7:35:c2:bb:57:34:90:7b:8b:22:16 +# SHA1 Fingerprint: 3e:2b:f7:f2:03:1b:96:f3:8c:e6:c4:d8:a8:5d:3e:2d:58:47:6a:0f +# SHA256 Fingerprint: c7:66:a9:be:f2:d4:07:1c:86:3a:31:aa:49:20:e8:13:b2:d1:98:60:8c:b7:b7:cf:e2:11:43:b8:36:df:09:ea +-----BEGIN CERTIFICATE----- +MIIHyTCCBbGgAwIBAgIBATANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEW +MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg +Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0NjM2WhcNMzYwOTE3MTk0NjM2WjB9 +MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMi +U2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3Rh +cnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZk +pMyONvg45iPwbm2xPN1yo4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rf +OQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/C +Ji/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/deMotHweXMAEtcnn6RtYT +Kqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt2PZE4XNi +HzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMM +Av+Z6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w ++2OqqGwaVLRcJXrJosmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+ +Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3 +Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVcUjyJthkqcwEKDwOzEmDyei+B +26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT37uMdBNSSwID +AQABo4ICUjCCAk4wDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAa4wHQYDVR0OBBYE +FE4L7xqkQFulF2mHMMo0aEPQQa7yMGQGA1UdHwRdMFswLKAqoCiGJmh0dHA6Ly9j +ZXJ0LnN0YXJ0Y29tLm9yZy9zZnNjYS1jcmwuY3JsMCugKaAnhiVodHRwOi8vY3Js +LnN0YXJ0Y29tLm9yZy9zZnNjYS1jcmwuY3JsMIIBXQYDVR0gBIIBVDCCAVAwggFM +BgsrBgEEAYG1NwEBATCCATswLwYIKwYBBQUHAgEWI2h0dHA6Ly9jZXJ0LnN0YXJ0 +Y29tLm9yZy9wb2xpY3kucGRmMDUGCCsGAQUFBwIBFilodHRwOi8vY2VydC5zdGFy +dGNvbS5vcmcvaW50ZXJtZWRpYXRlLnBkZjCB0AYIKwYBBQUHAgIwgcMwJxYgU3Rh +cnQgQ29tbWVyY2lhbCAoU3RhcnRDb20pIEx0ZC4wAwIBARqBl0xpbWl0ZWQgTGlh +YmlsaXR5LCByZWFkIHRoZSBzZWN0aW9uICpMZWdhbCBMaW1pdGF0aW9ucyogb2Yg +dGhlIFN0YXJ0Q29tIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFBvbGljeSBhdmFp +bGFibGUgYXQgaHR0cDovL2NlcnQuc3RhcnRjb20ub3JnL3BvbGljeS5wZGYwEQYJ +YIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilTdGFydENvbSBGcmVlIFNT +TCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQUFAAOCAgEAFmyZ +9GYMNPXQhV59CuzaEE44HF7fpiUFS5Eyweg78T3dRAlbB0mKKctmArexmvclmAk8 +jhvh3TaHK0u7aNM5Zj2gJsfyOZEdUauCe37Vzlrk4gNXcGmXCPleWKYK34wGmkUW +FjgKXlf2Ysd6AgXmvB618p70qSmD+LIU424oh0TDkBreOKk8rENNZEXO3SipXPJz +ewT4F+irsfMuXGRuczE6Eri8sxHkfY+BUZo7jYn0TZNmezwD7dOaHZrzZVD1oNB1 +ny+v8OqCQ5j4aZyJecRDjkZy42Q2Eq/3JR44iZB3fsNrarnDy0RLrHiQi+fHLB5L +EUTINFInzQpdn4XBidUaePKVEFMy3YCEZnXZtWgo+2EuvoSoOMCZEoalHmdkrQYu +L6lwhceWD3yJZfWOQ1QOq92lgDmUYMA0yZZwLKMS9R9Ie70cfmu3nZD0Ijuu+Pwq +yvqCUqDvr0tVk+vBtfAii6w0TiYiBKGHLHVKt+V9E9e4DGTANtLJL4YSjCMJwRuC +O3NJo2pXh5Tl1njFmUNj403gdy3hZZlyaQQaRwnmDwFWJPsfvw55qVguucQJAX6V +um0ABj6y6koQOdjQK/W/7HW/lwLFCRsI3FU34oH7N4RDYiDK51ZLZer+bMEkkySh +NOsF/5oirpt9P/FlUQqmMGqz9IgcgA38corog14= +-----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=GeoTrust Primary Certification Authority O=GeoTrust Inc. +# Subject: CN=GeoTrust Primary Certification Authority O=GeoTrust Inc. +# Label: "GeoTrust Primary Certification Authority" +# Serial: 32798226551256963324313806436981982369 +# MD5 Fingerprint: 02:26:c3:01:5e:08:30:37:43:a9:d0:7d:cf:37:e6:bf +# SHA1 Fingerprint: 32:3c:11:8e:1b:f7:b8:b6:52:54:e2:e2:10:0d:d6:02:90:37:f0:96 +# SHA256 Fingerprint: 37:d5:10:06:c5:12:ea:ab:62:64:21:f1:ec:8c:92:01:3f:c5:f8:2a:e9:8e:e5:33:eb:46:19:b8:de:b4:d0:6c +-----BEGIN CERTIFICATE----- +MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBY +MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMo +R2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEx +MjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgxCzAJBgNVBAYTAlVTMRYwFAYDVQQK +Ew1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQcmltYXJ5IENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9 +AWbK7hWNb6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjA +ZIVcFU2Ix7e64HXprQU9nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE0 +7e9GceBrAqg1cmuXm2bgyxx5X9gaBGgeRwLmnWDiNpcB3841kt++Z8dtd1k7j53W +kBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGttm/81w7a4DSwDRp35+MI +mO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G +A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJ +KoZIhvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ1 +6CePbJC/kRYkRj5KTs4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl +4b7UVXGYNTq+k+qurUKykG/g/CFNNWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6K +oKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHaFloxt/m0cYASSJlyc1pZU8Fj +UjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG1riR/aYNKxoU +AT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk= +-----END CERTIFICATE----- + +# Issuer: CN=thawte Primary Root CA O=thawte, Inc. OU=Certification Services Division/(c) 2006 thawte, Inc. - For authorized use only +# Subject: CN=thawte Primary Root CA O=thawte, Inc. OU=Certification Services Division/(c) 2006 thawte, Inc. - For authorized use only +# Label: "thawte Primary Root CA" +# Serial: 69529181992039203566298953787712940909 +# MD5 Fingerprint: 8c:ca:dc:0b:22:ce:f5:be:72:ac:41:1a:11:a8:d8:12 +# SHA1 Fingerprint: 91:c6:d6:ee:3e:8a:c8:63:84:e5:48:c2:99:29:5c:75:6c:81:7b:81 +# SHA256 Fingerprint: 8d:72:2f:81:a9:c1:13:c0:79:1d:f1:36:a2:96:6d:b2:6c:95:0a:97:1d:b4:6b:41:99:f4:ea:54:b7:8b:fb:9f +-----BEGIN CERTIFICATE----- +MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCB +qTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf +Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw +MDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNV +BAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3MDAwMDAwWhcNMzYw +NzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5j +LjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYG +A1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl +IG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCsoPD7gFnUnMekz52hWXMJEEUMDSxuaPFs +W0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ1CRfBsDMRJSUjQJib+ta +3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGcq/gcfomk +6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6 +Sk/KaAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94J +NqR32HuHUETVPm4pafs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBA +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XP +r87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUFAAOCAQEAeRHAS7ORtvzw6WfU +DW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeEuzLlQRHAd9mz +YJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX +xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2 +/qxAeeWsEG89jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/ +LHbTY5xZ3Y+m4Q6gLkH3LpVHz7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7 +jVaMaA== +-----END CERTIFICATE----- + +# Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G5 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2006 VeriSign, Inc. - For authorized use only +# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G5 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2006 VeriSign, Inc. - For authorized use only +# Label: "VeriSign Class 3 Public Primary Certification Authority - G5" +# Serial: 33037644167568058970164719475676101450 +# MD5 Fingerprint: cb:17:e4:31:67:3e:e2:09:fe:45:57:93:f3:0a:fa:1c +# SHA1 Fingerprint: 4e:b6:d5:78:49:9b:1c:cf:5f:58:1e:ad:56:be:3d:9b:67:44:a5:e5 +# SHA256 Fingerprint: 9a:cf:ab:7e:43:c8:d8:80:d0:6b:26:2a:94:de:ee:e4:b4:65:99:89:c3:d0:ca:f1:9b:af:64:05:e4:1a:b7:df +-----BEGIN CERTIFICATE----- +MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCB +yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL +ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp +U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW +ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0 +aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCByjEL +MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW +ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2ln +biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp +U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y +aXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1 +nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbex +t0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIz +SdhDY2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQG +BO+QueQA5N06tRn/Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+ +rCpSx4/VBEnkjWNHiDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/ +NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E +BAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAH +BgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy +aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKv +MzEzMA0GCSqGSIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzE +p6B4Eq1iDkVwZMXnl2YtmAl+X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y +5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKEKQsTb47bDN0lAtukixlE0kF6BWlK +WE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiCKm0oHw0LxOXnGiYZ +4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vEZV8N +hnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq +-----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=Network Solutions Certificate Authority O=Network Solutions L.L.C. +# Subject: CN=Network Solutions Certificate Authority O=Network Solutions L.L.C. +# Label: "Network Solutions Certificate Authority" +# Serial: 116697915152937497490437556386812487904 +# MD5 Fingerprint: d3:f3:a6:16:c0:fa:6b:1d:59:b1:2d:96:4d:0e:11:2e +# SHA1 Fingerprint: 74:f8:a3:c3:ef:e7:b3:90:06:4b:83:90:3c:21:64:60:20:e5:df:ce +# SHA256 Fingerprint: 15:f0:ba:00:a3:ac:7a:f3:ac:88:4c:07:2b:10:11:a0:77:bd:77:c0:97:f4:01:64:b2:f8:59:8a:bd:83:86:0c +-----BEGIN CERTIFICATE----- +MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBi +MQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu +MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3Jp +dHkwHhcNMDYxMjAxMDAwMDAwWhcNMjkxMjMxMjM1OTU5WjBiMQswCQYDVQQGEwJV +UzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYDVQQDEydO +ZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkvH6SMG3G2I4rC7xGzuAnlt7e+foS0zwz +c7MEL7xxjOWftiJgPl9dzgn/ggwbmlFQGiaJ3dVhXRncEg8tCqJDXRfQNJIg6nPP +OCwGJgl6cvf6UDL4wpPTaaIjzkGxzOTVHzbRijr4jGPiFFlp7Q3Tf2vouAPlT2rl +mGNpSAW+Lv8ztumXWWn4Zxmuk2GWRBXTcrA/vGp97Eh/jcOrqnErU2lBUzS1sLnF +BgrEsEX1QV1uiUV7PTsmjHTC5dLRfbIR1PtYMiKagMnc/Qzpf14Dl847ABSHJ3A4 +qY5usyd2mFHgBeMhqxrVhSI8KbWaFsWAqPS7azCPL0YCorEMIuDTAgMBAAGjgZcw +gZQwHQYDVR0OBBYEFCEwyfsA106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB/wQEAwIB +BjAPBgNVHRMBAf8EBTADAQH/MFIGA1UdHwRLMEkwR6BFoEOGQWh0dHA6Ly9jcmwu +bmV0c29sc3NsLmNvbS9OZXR3b3JrU29sdXRpb25zQ2VydGlmaWNhdGVBdXRob3Jp +dHkuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQC7rkvnt1frf6ott3NHhWrB5KUd5Oc8 +6fRZZXe1eltajSU24HqXLjjAV2CDmAaDn7l2em5Q4LqILPxFzBiwmZVRDuwduIj/ +h1AcgsLj4DKAv6ALR8jDMe+ZZzKATxcheQxpXN5eNK4CtSbqUN9/GGUsyfJj4akH +/nxxH2szJGoeBfcFaMBqEssuXmHLrijTfsK0ZpEmXzwuJF/LWA/rKOyvEZbz3Htv +wKeI8lN3s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHN +pGxlaKFJdlxDydi8NmdspZS11My5vWo1ViHe2MPr+8ukYEywVaCge1ey +-----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=TC TrustCenter Class 2 CA II O=TC TrustCenter GmbH OU=TC TrustCenter Class 2 CA +# Subject: CN=TC TrustCenter Class 2 CA II O=TC TrustCenter GmbH OU=TC TrustCenter Class 2 CA +# Label: "TC TrustCenter Class 2 CA II" +# Serial: 941389028203453866782103406992443 +# MD5 Fingerprint: ce:78:33:5c:59:78:01:6e:18:ea:b9:36:a0:b9:2e:23 +# SHA1 Fingerprint: ae:50:83:ed:7c:f4:5c:bc:8f:61:c6:21:fe:68:5d:79:42:21:15:6e +# SHA256 Fingerprint: e6:b8:f8:76:64:85:f8:07:ae:7f:8d:ac:16:70:46:1f:07:c0:a1:3e:ef:3a:1f:f7:17:53:8d:7a:ba:d3:91:b4 +-----BEGIN CERTIFICATE----- +MIIEqjCCA5KgAwIBAgIOLmoAAQACH9dSISwRXDswDQYJKoZIhvcNAQEFBQAwdjEL +MAkGA1UEBhMCREUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNV +BAsTGVRDIFRydXN0Q2VudGVyIENsYXNzIDIgQ0ExJTAjBgNVBAMTHFRDIFRydXN0 +Q2VudGVyIENsYXNzIDIgQ0EgSUkwHhcNMDYwMTEyMTQzODQzWhcNMjUxMjMxMjI1 +OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1c3RDZW50ZXIgR21i +SDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQTElMCMGA1UEAxMc +VEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAKuAh5uO8MN8h9foJIIRszzdQ2Lu+MNF2ujhoF/RKrLqk2jf +tMjWQ+nEdVl//OEd+DFwIxuInie5e/060smp6RQvkL4DUsFJzfb95AhmC1eKokKg +uNV/aVyQMrKXDcpK3EY+AlWJU+MaWss2xgdW94zPEfRMuzBwBJWl9jmM/XOBCH2J +XjIeIqkiRUuwZi4wzJ9l/fzLganx4Duvo4bRierERXlQXa7pIXSSTYtZgo+U4+lK +8edJsBTj9WLL1XK9H7nSn6DNqPoByNkN39r8R52zyFTfSUrxIan+GE7uSNQZu+99 +5OKdy1u2bv/jzVrndIIFuoAlOMvkaZ6vQaoahPUCAwEAAaOCATQwggEwMA8GA1Ud +EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTjq1RMgKHbVkO3 +kUrL84J6E1wIqzCB7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRy +dXN0Y2VudGVyLmRlL2NybC92Mi90Y19jbGFzc18yX2NhX0lJLmNybIaBn2xkYXA6 +Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBUcnVzdENlbnRlciUyMENsYXNz +JTIwMiUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21iSCxPVT1yb290 +Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u +TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEAjNfffu4bgBCzg/XbEeprS6iS +GNn3Bzn1LL4GdXpoUxUc6krtXvwjshOg0wn/9vYua0Fxec3ibf2uWWuFHbhOIprt +ZjluS5TmVfwLG4t3wVMTZonZKNaL80VKY7f9ewthXbhtvsPcW3nS7Yblok2+XnR8 +au0WOB9/WIFaGusyiC2y8zl3gK9etmF1KdsjTYjKUCjLhdLTEKJZbtOTVAB6okaV +hgWcqRmY5TFyDADiZ9lA4CQze28suVyrZZ0srHbqNZn1l7kPJOzHdiEoZa5X6AeI +dUpWoNIFOqTmjZKILPPy4cHGYdtBxceb9w4aUUXCYWvcZCcXjFq32nQozZfkvQ== +-----END CERTIFICATE----- + +# Issuer: CN=TC TrustCenter Class 3 CA II O=TC TrustCenter GmbH OU=TC TrustCenter Class 3 CA +# Subject: CN=TC TrustCenter Class 3 CA II O=TC TrustCenter GmbH OU=TC TrustCenter Class 3 CA +# Label: "TC TrustCenter Class 3 CA II" +# Serial: 1506523511417715638772220530020799 +# MD5 Fingerprint: 56:5f:aa:80:61:12:17:f6:67:21:e6:2b:6d:61:56:8e +# SHA1 Fingerprint: 80:25:ef:f4:6e:70:c8:d4:72:24:65:84:fe:40:3b:8a:8d:6a:db:f5 +# SHA256 Fingerprint: 8d:a0:84:fc:f9:9c:e0:77:22:f8:9b:32:05:93:98:06:fa:5c:b8:11:e1:c8:13:f6:a1:08:c7:d3:36:b3:40:8e +-----BEGIN CERTIFICATE----- +MIIEqjCCA5KgAwIBAgIOSkcAAQAC5aBd1j8AUb8wDQYJKoZIhvcNAQEFBQAwdjEL +MAkGA1UEBhMCREUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNV +BAsTGVRDIFRydXN0Q2VudGVyIENsYXNzIDMgQ0ExJTAjBgNVBAMTHFRDIFRydXN0 +Q2VudGVyIENsYXNzIDMgQ0EgSUkwHhcNMDYwMTEyMTQ0MTU3WhcNMjUxMjMxMjI1 +OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1c3RDZW50ZXIgR21i +SDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQTElMCMGA1UEAxMc +VEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBALTgu1G7OVyLBMVMeRwjhjEQY0NVJz/GRcekPewJDRoeIMJW +Ht4bNwcwIi9v8Qbxq63WyKthoy9DxLCyLfzDlml7forkzMA5EpBCYMnMNWju2l+Q +Vl/NHE1bWEnrDgFPZPosPIlY2C8u4rBo6SI7dYnWRBpl8huXJh0obazovVkdKyT2 +1oQDZogkAHhg8fir/gKya/si+zXmFtGt9i4S5Po1auUZuV3bOx4a+9P/FRQI2Alq +ukWdFHlgfa9Aigdzs5OW03Q0jTo3Kd5c7PXuLjHCINy+8U9/I1LZW+Jk2ZyqBwi1 +Rb3R0DHBq1SfqdLDYmAD8bs5SpJKPQq5ncWg/jcCAwEAAaOCATQwggEwMA8GA1Ud +EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTUovyfs8PYA9NX +XAek0CSnwPIA1DCB7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRy +dXN0Y2VudGVyLmRlL2NybC92Mi90Y19jbGFzc18zX2NhX0lJLmNybIaBn2xkYXA6 +Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBUcnVzdENlbnRlciUyMENsYXNz +JTIwMyUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21iSCxPVT1yb290 +Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u +TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEANmDkcPcGIEPZIxpC8vijsrlN +irTzwppVMXzEO2eatN9NDoqTSheLG43KieHPOh6sHfGcMrSOWXaiQYUlN6AT0PV8 +TtXqluJucsG7Kv5sbviRmEb8yRtXW+rIGjs/sFGYPAfaLFkB2otE6OF0/ado3VS6 +g0bsyEa1+K+XwDsJHI/OcpY9M1ZwvJbL2NV9IJqDnxrcOfHFcqMRA/07QlIp2+gB +95tejNaNhk4Z+rwcvsUhpYeeeC422wlxo3I0+GzjBgnyXlal092Y+tTmBvTwtiBj +S+opvaqCZh77gaqnN60TGOaSw4HBM7uIHqHn4rS9MWwOUT1v+5ZWgOI2F9Hc5A== +-----END CERTIFICATE----- + +# Issuer: CN=TC TrustCenter Universal CA I O=TC TrustCenter GmbH OU=TC TrustCenter Universal CA +# Subject: CN=TC TrustCenter Universal CA I O=TC TrustCenter GmbH OU=TC TrustCenter Universal CA +# Label: "TC TrustCenter Universal CA I" +# Serial: 601024842042189035295619584734726 +# MD5 Fingerprint: 45:e1:a5:72:c5:a9:36:64:40:9e:f5:e4:58:84:67:8c +# SHA1 Fingerprint: 6b:2f:34:ad:89:58:be:62:fd:b0:6b:5c:ce:bb:9d:d9:4f:4e:39:f3 +# SHA256 Fingerprint: eb:f3:c0:2a:87:89:b1:fb:7d:51:19:95:d6:63:b7:29:06:d9:13:ce:0d:5e:10:56:8a:8a:77:e2:58:61:67:e7 +-----BEGIN CERTIFICATE----- +MIID3TCCAsWgAwIBAgIOHaIAAQAC7LdggHiNtgYwDQYJKoZIhvcNAQEFBQAweTEL +MAkGA1UEBhMCREUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxJDAiBgNV +BAsTG1RDIFRydXN0Q2VudGVyIFVuaXZlcnNhbCBDQTEmMCQGA1UEAxMdVEMgVHJ1 +c3RDZW50ZXIgVW5pdmVyc2FsIENBIEkwHhcNMDYwMzIyMTU1NDI4WhcNMjUxMjMx +MjI1OTU5WjB5MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1c3RDZW50ZXIg +R21iSDEkMCIGA1UECxMbVEMgVHJ1c3RDZW50ZXIgVW5pdmVyc2FsIENBMSYwJAYD +VQQDEx1UQyBUcnVzdENlbnRlciBVbml2ZXJzYWwgQ0EgSTCCASIwDQYJKoZIhvcN +AQEBBQADggEPADCCAQoCggEBAKR3I5ZEr5D0MacQ9CaHnPM42Q9e3s9B6DGtxnSR +JJZ4Hgmgm5qVSkr1YnwCqMqs+1oEdjneX/H5s7/zA1hV0qq34wQi0fiU2iIIAI3T +fCZdzHd55yx4Oagmcw6iXSVphU9VDprvxrlE4Vc93x9UIuVvZaozhDrzznq+VZeu +jRIPFDPiUHDDSYcTvFHe15gSWu86gzOSBnWLknwSaHtwag+1m7Z3W0hZneTvWq3z +wZ7U10VOylY0Ibw+F1tvdwxIAUMpsN0/lm7mlaoMwCC2/T42J5zjXM9OgdwZu5GQ +fezmlwQek8wiSdeXhrYTCjxDI3d+8NzmzSQfO4ObNDqDNOMCAwEAAaNjMGEwHwYD +VR0jBBgwFoAUkqR1LKSevoFE63n8isWVpesQdXMwDwYDVR0TAQH/BAUwAwEB/zAO +BgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFJKkdSyknr6BROt5/IrFlaXrEHVzMA0G +CSqGSIb3DQEBBQUAA4IBAQAo0uCG1eb4e/CX3CJrO5UUVg8RMKWaTzqwOuAGy2X1 +7caXJ/4l8lfmXpWMPmRgFVp/Lw0BxbFg/UU1z/CyvwbZ71q+s2IhtNerNXxTPqYn +8aEt2hojnczd7Dwtnic0XQ/CNnm8yUpiLe1r2X1BQ3y2qsrtYbE3ghUJGooWMNjs +ydZHcnhLEEYUjl8Or+zHL6sQ17bxbuyGssLoDZJz3KL0Dzq/YSMQiZxIQG5wALPT +ujdEWBF6AmqI8Dc08BnprNRlc/ZpjGSUOnmFKbAWKwyCPwacx/0QK54PLLae4xW/ +2TYcuiUaUj0a7CIMHOCkoj3w6DnPgcB77V0fb8XQC9eY +-----END CERTIFICATE----- + +# Issuer: CN=Cybertrust Global Root O=Cybertrust, Inc +# Subject: CN=Cybertrust Global Root O=Cybertrust, Inc +# Label: "Cybertrust Global Root" +# Serial: 4835703278459682877484360 +# MD5 Fingerprint: 72:e4:4a:87:e3:69:40:80:77:ea:bc:e3:f4:ff:f0:e1 +# SHA1 Fingerprint: 5f:43:e5:b1:bf:f8:78:8c:ac:1c:c7:ca:4a:9a:c6:22:2b:cc:34:c6 +# SHA256 Fingerprint: 96:0a:df:00:63:e9:63:56:75:0c:29:65:dd:0a:08:67:da:0b:9c:bd:6e:77:71:4a:ea:fb:23:49:ab:39:3d:a3 +-----BEGIN CERTIFICATE----- +MIIDoTCCAomgAwIBAgILBAAAAAABD4WqLUgwDQYJKoZIhvcNAQEFBQAwOzEYMBYG +A1UEChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2Jh +bCBSb290MB4XDTA2MTIxNTA4MDAwMFoXDTIxMTIxNTA4MDAwMFowOzEYMBYGA1UE +ChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2JhbCBS +b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+Mi8vRRQZhP/8NN5 +7CPytxrHjoXxEnOmGaoQ25yiZXRadz5RfVb23CO21O1fWLE3TdVJDm71aofW0ozS +J8bi/zafmGWgE07GKmSb1ZASzxQG9Dvj1Ci+6A74q05IlG2OlTEQXO2iLb3VOm2y +HLtgwEZLAfVJrn5GitB0jaEMAs7u/OePuGtm839EAL9mJRQr3RAwHQeWP032a7iP +t3sMpTjr3kfb1V05/Iin89cqdPHoWqI7n1C6poxFNcJQZZXcY4Lv3b93TZxiyWNz +FtApD0mpSPCzqrdsxacwOUBdrsTiXSZT8M4cIwhhqJQZugRiQOwfOHB3EgZxpzAY +XSUnpQIDAQABo4GlMIGiMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/ +MB0GA1UdDgQWBBS2CHsNesysIEyGVjJez6tuhS1wVzA/BgNVHR8EODA2MDSgMqAw +hi5odHRwOi8vd3d3Mi5wdWJsaWMtdHJ1c3QuY29tL2NybC9jdC9jdHJvb3QuY3Js +MB8GA1UdIwQYMBaAFLYIew16zKwgTIZWMl7Pq26FLXBXMA0GCSqGSIb3DQEBBQUA +A4IBAQBW7wojoFROlZfJ+InaRcHUowAl9B8Tq7ejhVhpwjCt2BWKLePJzYFa+HMj +Wqd8BfP9IjsO0QbE2zZMcwSO5bAi5MXzLqXZI+O4Tkogp24CJJ8iYGd7ix1yCcUx +XOl5n4BHPa2hCwcUPUf/A2kaDAtE52Mlp3+yybh2hO0j9n0Hq0V+09+zv+mKts2o +omcrUtW3ZfA5TGOgkXmTUg9U3YO7n9GPp1Nzw8v/MOx8BLjYRB+TX3EJIrduPuoc +A06dGiBh+4E37F78CkWr1+cXVdCg6mCbpvbjjFspwgZgFJ0tl0ypkxWdYcQBX0jW +WL1WMRJOEcgh4LMRkWXbtKaIOM5V +-----END CERTIFICATE----- + +# Issuer: CN=GeoTrust Primary Certification Authority - G3 O=GeoTrust Inc. OU=(c) 2008 GeoTrust Inc. - For authorized use only +# Subject: CN=GeoTrust Primary Certification Authority - G3 O=GeoTrust Inc. OU=(c) 2008 GeoTrust Inc. - For authorized use only +# Label: "GeoTrust Primary Certification Authority - G3" +# Serial: 28809105769928564313984085209975885599 +# MD5 Fingerprint: b5:e8:34:36:c9:10:44:58:48:70:6d:2e:83:d4:b8:05 +# SHA1 Fingerprint: 03:9e:ed:b8:0b:e7:a0:3c:69:53:89:3b:20:d2:d9:32:3a:4c:2a:fd +# SHA256 Fingerprint: b4:78:b8:12:25:0d:f8:78:63:5c:2a:a7:ec:7d:15:5e:aa:62:5e:e8:29:16:e2:cd:29:43:61:88:6c:d1:fb:d4 +-----BEGIN CERTIFICATE----- +MIID/jCCAuagAwIBAgIQFaxulBmyeUtB9iepwxgPHzANBgkqhkiG9w0BAQsFADCB +mDELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsT +MChjKSAyMDA4IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25s +eTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhv +cml0eSAtIEczMB4XDTA4MDQwMjAwMDAwMFoXDTM3MTIwMTIzNTk1OVowgZgxCzAJ +BgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykg +MjAwOCBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0 +BgNVBAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg +LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANziXmJYHTNXOTIz ++uvLh4yn1ErdBojqZI4xmKU4kB6Yzy5jK/BGvESyiaHAKAxJcCGVn2TAppMSAmUm +hsalifD614SgcK9PGpc/BkTVyetyEH3kMSj7HGHmKAdEc5IiaacDiGydY8hS2pgn +5whMcD60yRLBxWeDXTPzAxHsatBT4tG6NmCUgLthY2xbF37fQJQeqw3CIShwiP/W +JmxsYAQlTlV+fe+/lEjetx3dcI0FX4ilm/LC7urRQEFtYjgdVgbFA0dRIBn8exAL +DmKudlW/X3e+PkkBUz2YJQN2JFodtNuJ6nnltrM7P7pMKEF/BqxqjsHQ9gUdfeZC +huOl1UcCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw +HQYDVR0OBBYEFMR5yo6hTgMdHNxr2zFblD4/MH8tMA0GCSqGSIb3DQEBCwUAA4IB +AQAtxRPPVoB7eni9n64smefv2t+UXglpp+duaIy9cr5HqQ6XErhK8WTTOd8lNNTB +zU6B8A8ExCSzNJbGpqow32hhc9f5joWJ7w5elShKKiePEI4ufIbEAp7aDHdlDkQN +kv39sxY2+hENHYwOB4lqKVb3cvTdFZx3NWZXqxNT2I7BQMXXExZacse3aQHEerGD +AWh9jUGhlBjBJVz88P6DAod8DQ3PLghcSkANPuyBYeYk28rgDi0Hsj5W3I31QYUH +SJsMC8tJP33st/3LjWeJGqvtux6jAAgIFyqCXDFdRootD4abdNlF+9RAsXqqaC2G +spki4cErx5z481+oghLrGREt +-----END CERTIFICATE----- + +# Issuer: CN=thawte Primary Root CA - G2 O=thawte, Inc. OU=(c) 2007 thawte, Inc. - For authorized use only +# Subject: CN=thawte Primary Root CA - G2 O=thawte, Inc. OU=(c) 2007 thawte, Inc. - For authorized use only +# Label: "thawte Primary Root CA - G2" +# Serial: 71758320672825410020661621085256472406 +# MD5 Fingerprint: 74:9d:ea:60:24:c4:fd:22:53:3e:cc:3a:72:d9:29:4f +# SHA1 Fingerprint: aa:db:bc:22:23:8f:c4:01:a1:27:bb:38:dd:f4:1d:db:08:9e:f0:12 +# SHA256 Fingerprint: a4:31:0d:50:af:18:a6:44:71:90:37:2a:86:af:af:8b:95:1f:fb:43:1d:83:7f:1e:56:88:b4:59:71:ed:15:57 +-----BEGIN CERTIFICATE----- +MIICiDCCAg2gAwIBAgIQNfwmXNmET8k9Jj1Xm67XVjAKBggqhkjOPQQDAzCBhDEL +MAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjE4MDYGA1UECxMvKGMp +IDIwMDcgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAi +BgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMjAeFw0wNzExMDUwMDAw +MDBaFw0zODAxMTgyMzU5NTlaMIGEMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhh +d3RlLCBJbmMuMTgwNgYDVQQLEy8oYykgMjAwNyB0aGF3dGUsIEluYy4gLSBGb3Ig +YXV0aG9yaXplZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9v +dCBDQSAtIEcyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEotWcgnuVnfFSeIf+iha/ +BebfowJPDQfGAFG6DAJSLSKkQjnE/o/qycG+1E3/n3qe4rF8mq2nhglzh9HnmuN6 +papu+7qzcMBniKI11KOasf2twu8x+qi58/sIxpHR+ymVo0IwQDAPBgNVHRMBAf8E +BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUmtgAMADna3+FGO6Lts6K +DPgR4bswCgYIKoZIzj0EAwMDaQAwZgIxAN344FdHW6fmCsO99YCKlzUNG4k8VIZ3 +KMqh9HneteY4sPBlcIx/AlTCv//YoT7ZzwIxAMSNlPzcU9LcnXgWHxUzI1NS41ox +XZ3Krr0TKUQNJ1uo52icEvdYPy5yAlejj6EULg== +-----END CERTIFICATE----- + +# Issuer: CN=thawte Primary Root CA - G3 O=thawte, Inc. OU=Certification Services Division/(c) 2008 thawte, Inc. - For authorized use only +# Subject: CN=thawte Primary Root CA - G3 O=thawte, Inc. OU=Certification Services Division/(c) 2008 thawte, Inc. - For authorized use only +# Label: "thawte Primary Root CA - G3" +# Serial: 127614157056681299805556476275995414779 +# MD5 Fingerprint: fb:1b:5d:43:8a:94:cd:44:c6:76:f2:43:4b:47:e7:31 +# SHA1 Fingerprint: f1:8b:53:8d:1b:e9:03:b6:a6:f0:56:43:5b:17:15:89:ca:f3:6b:f2 +# SHA256 Fingerprint: 4b:03:f4:58:07:ad:70:f2:1b:fc:2c:ae:71:c9:fd:e4:60:4c:06:4c:f5:ff:b6:86:ba:e5:db:aa:d7:fd:d3:4c +-----BEGIN CERTIFICATE----- +MIIEKjCCAxKgAwIBAgIQYAGXt0an6rS0mtZLL/eQ+zANBgkqhkiG9w0BAQsFADCB +rjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf +Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw +MDggdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAiBgNV +BAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMzAeFw0wODA0MDIwMDAwMDBa +Fw0zNzEyMDEyMzU5NTlaMIGuMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhhd3Rl +LCBJbmMuMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9u +MTgwNgYDVQQLEy8oYykgMjAwOCB0aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXpl +ZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAtIEcz +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsr8nLPvb2FvdeHsbnndm +gcs+vHyu86YnmjSjaDFxODNi5PNxZnmxqWWjpYvVj2AtP0LMqmsywCPLLEHd5N/8 +YZzic7IilRFDGF/Eth9XbAoFWCLINkw6fKXRz4aviKdEAhN0cXMKQlkC+BsUa0Lf +b1+6a4KinVvnSr0eAXLbS3ToO39/fR8EtCab4LRarEc9VbjXsCZSKAExQGbY2SS9 +9irY7CFJXJv2eul/VTV+lmuNk5Mny5K76qxAwJ/C+IDPXfRa3M50hqY+bAtTyr2S +zhkGcuYMXDhpxwTWvGzOW/b3aJzcJRVIiKHpqfiYnODz1TEoYRFsZ5aNOZnLwkUk +OQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNV +HQ4EFgQUrWyqlGCc7eT/+j4KdCtjA/e2Wb8wDQYJKoZIhvcNAQELBQADggEBABpA +2JVlrAmSicY59BDlqQ5mU1143vokkbvnRFHfxhY0Cu9qRFHqKweKA3rD6z8KLFIW +oCtDuSWQP3CpMyVtRRooOyfPqsMpQhvfO0zAMzRbQYi/aytlryjvsvXDqmbOe1bu +t8jLZ8HJnBoYuMTDSQPxYA5QzUbF83d597YV4Djbxy8ooAw/dyZ02SUS2jHaGh7c +KUGRIjxpp7sC8rZcJwOJ9Abqm+RyguOhCcHpABnTPtRwa7pxpqpYrvS76Wy274fM +m7v/OeZWYdMKp8RcTGB7BXcmer/YB1IsYvdwY9k5vG8cwnncdimvzsUsZAReiDZu +MdRAGmI0Nj81Aa6sY6A= +-----END CERTIFICATE----- + +# Issuer: CN=GeoTrust Primary Certification Authority - G2 O=GeoTrust Inc. OU=(c) 2007 GeoTrust Inc. - For authorized use only +# Subject: CN=GeoTrust Primary Certification Authority - G2 O=GeoTrust Inc. OU=(c) 2007 GeoTrust Inc. - For authorized use only +# Label: "GeoTrust Primary Certification Authority - G2" +# Serial: 80682863203381065782177908751794619243 +# MD5 Fingerprint: 01:5e:d8:6b:bd:6f:3d:8e:a1:31:f8:12:e0:98:73:6a +# SHA1 Fingerprint: 8d:17:84:d5:37:f3:03:7d:ec:70:fe:57:8b:51:9a:99:e6:10:d7:b0 +# SHA256 Fingerprint: 5e:db:7a:c4:3b:82:a0:6a:87:61:e8:d7:be:49:79:eb:f2:61:1f:7d:d7:9b:f9:1c:1c:6b:56:6a:21:9e:d7:66 +-----BEGIN CERTIFICATE----- +MIICrjCCAjWgAwIBAgIQPLL0SAoA4v7rJDteYD7DazAKBggqhkjOPQQDAzCBmDEL +MAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChj +KSAyMDA3IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2 +MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0 +eSAtIEcyMB4XDTA3MTEwNTAwMDAwMFoXDTM4MDExODIzNTk1OVowgZgxCzAJBgNV +BAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykgMjAw +NyBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNV +BAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBH +MjB2MBAGByqGSM49AgEGBSuBBAAiA2IABBWx6P0DFUPlrOuHNxFi79KDNlJ9RVcL +So17VDs6bl8VAsBQps8lL33KSLjHUGMcKiEIfJo22Av+0SbFWDEwKCXzXV2juLal +tJLtbCyf691DiaI8S0iRHVDsJt/WYC69IaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO +BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBVfNVdRVfslsq0DafwBo/q+EVXVMAoG +CCqGSM49BAMDA2cAMGQCMGSWWaboCd6LuvpaiIjwH5HTRqjySkwCY/tsXzjbLkGT +qQ7mndwxHLKgpxgceeHHNgIwOlavmnRs9vuD4DPTCF+hnMJbn0bWtsuRBmOiBucz +rD6ogRLQy7rQkgu2npaqBA+K +-----END CERTIFICATE----- + +# Issuer: CN=VeriSign Universal Root Certification Authority O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2008 VeriSign, Inc. - For authorized use only +# Subject: CN=VeriSign Universal Root Certification Authority O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2008 VeriSign, Inc. - For authorized use only +# Label: "VeriSign Universal Root Certification Authority" +# Serial: 85209574734084581917763752644031726877 +# MD5 Fingerprint: 8e:ad:b5:01:aa:4d:81:e4:8c:1d:d1:e1:14:00:95:19 +# SHA1 Fingerprint: 36:79:ca:35:66:87:72:30:4d:30:a5:fb:87:3b:0f:a7:7b:b7:0d:54 +# SHA256 Fingerprint: 23:99:56:11:27:a5:71:25:de:8c:ef:ea:61:0d:df:2f:a0:78:b5:c8:06:7f:4e:82:82:90:bf:b8:60:e8:4b:3c +-----BEGIN CERTIFICATE----- +MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCB +vTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL +ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJp +U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MTgwNgYDVQQDEy9W +ZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe +Fw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJVUzEX +MBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0 +IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9y +IGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNh +bCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj1mCOkdeQmIN65lgZOIzF +9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGPMiJhgsWH +H26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+H +LL729fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN +/BMReYTtXlT2NJ8IAfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPT +rJ9VAMf2CGqUuV/c4DPxhGD5WycRtPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1Ud +EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0GCCsGAQUFBwEMBGEwX6FdoFsw +WTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2Oa8PPgGrUSBgs +exkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud +DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4 +sAPmLGd75JR3Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+ +seQxIcaBlVZaDrHC1LGmWazxY8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz +4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTxP/jgdFcrGJ2BtMQo2pSXpXDrrB2+ +BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+PwGZsY6rp2aQW9IHR +lRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4mJO3 +7M2CYfE45k+XmCpajQ== +-----END CERTIFICATE----- + +# Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G4 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2007 VeriSign, Inc. - For authorized use only +# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G4 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2007 VeriSign, Inc. - For authorized use only +# Label: "VeriSign Class 3 Public Primary Certification Authority - G4" +# Serial: 63143484348153506665311985501458640051 +# MD5 Fingerprint: 3a:52:e1:e7:fd:6f:3a:e3:6f:f3:6f:99:1b:f9:22:41 +# SHA1 Fingerprint: 22:d5:d8:df:8f:02:31:d1:8d:f7:9d:b7:cf:8a:2d:64:c9:3f:6c:3a +# SHA256 Fingerprint: 69:dd:d7:ea:90:bb:57:c9:3e:13:5d:c8:5e:a6:fc:d5:48:0b:60:32:39:bd:c4:54:fc:75:8b:2a:26:cf:7f:79 +-----BEGIN CERTIFICATE----- +MIIDhDCCAwqgAwIBAgIQL4D+I4wOIg9IZxIokYesszAKBggqhkjOPQQDAzCByjEL +MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW +ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2ln +biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp +U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y +aXR5IC0gRzQwHhcNMDcxMTA1MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCByjELMAkG +A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJp +U2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwg +SW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2ln +biBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +IC0gRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASnVnp8Utpkmw4tXNherJI9/gHm +GUo9FANL+mAnINmDiWn6VMaaGF5VKmTeBvaNSjutEDxlPZCIBIngMGGzrl0Bp3ve +fLK+ymVhAIau2o970ImtTR1ZmkGxvEeA3J5iw/mjgbIwga8wDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJ +aW1hZ2UvZ2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYj +aHR0cDovL2xvZ28udmVyaXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFLMW +kf3upm7ktS5Jj4d4gYDs5bG1MAoGCCqGSM49BAMDA2gAMGUCMGYhDBgmYFo4e1ZC +4Kf8NoRRkSAsdk1DPcQdhCPQrNZ8NQbOzWm9kA3bbEhCHQ6qQgIxAJw9SDkjOVga +FRJZap7v1VmyHVIsmXHNxynfGyphe3HR3vPA5Q06Sqotp9iGKt0uEA== +-----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=TC TrustCenter Universal CA III O=TC TrustCenter GmbH OU=TC TrustCenter Universal CA +# Subject: CN=TC TrustCenter Universal CA III O=TC TrustCenter GmbH OU=TC TrustCenter Universal CA +# Label: "TC TrustCenter Universal CA III" +# Serial: 2010889993983507346460533407902964 +# MD5 Fingerprint: 9f:dd:db:ab:ff:8e:ff:45:21:5f:f0:6c:9d:8f:fe:2b +# SHA1 Fingerprint: 96:56:cd:7b:57:96:98:95:d0:e1:41:46:68:06:fb:b8:c6:11:06:87 +# SHA256 Fingerprint: 30:9b:4a:87:f6:ca:56:c9:31:69:aa:a9:9c:6d:98:88:54:d7:89:2b:d5:43:7e:2d:07:b2:9c:be:da:55:d3:5d +-----BEGIN CERTIFICATE----- +MIID4TCCAsmgAwIBAgIOYyUAAQACFI0zFQLkbPQwDQYJKoZIhvcNAQEFBQAwezEL +MAkGA1UEBhMCREUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxJDAiBgNV +BAsTG1RDIFRydXN0Q2VudGVyIFVuaXZlcnNhbCBDQTEoMCYGA1UEAxMfVEMgVHJ1 +c3RDZW50ZXIgVW5pdmVyc2FsIENBIElJSTAeFw0wOTA5MDkwODE1MjdaFw0yOTEy +MzEyMzU5NTlaMHsxCzAJBgNVBAYTAkRFMRwwGgYDVQQKExNUQyBUcnVzdENlbnRl +ciBHbWJIMSQwIgYDVQQLExtUQyBUcnVzdENlbnRlciBVbml2ZXJzYWwgQ0ExKDAm +BgNVBAMTH1RDIFRydXN0Q2VudGVyIFVuaXZlcnNhbCBDQSBJSUkwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDC2pxisLlxErALyBpXsq6DFJmzNEubkKLF +5+cvAqBNLaT6hdqbJYUtQCggbergvbFIgyIpRJ9Og+41URNzdNW88jBmlFPAQDYv +DIRlzg9uwliT6CwLOunBjvvya8o84pxOjuT5fdMnnxvVZ3iHLX8LR7PH6MlIfK8v +zArZQe+f/prhsq75U7Xl6UafYOPfjdN/+5Z+s7Vy+EutCHnNaYlAJ/Uqwa1D7KRT +yGG299J5KmcYdkhtWyUB0SbFt1dpIxVbYYqt8Bst2a9c8SaQaanVDED1M4BDj5yj +dipFtK+/fz6HP3bFzSreIMUWWMv5G/UPyw0RUmS40nZid4PxWJ//AgMBAAGjYzBh +MB8GA1UdIwQYMBaAFFbn4VslQ4Dg9ozhcbyO5YAvxEjiMA8GA1UdEwEB/wQFMAMB +Af8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRW5+FbJUOA4PaM4XG8juWAL8RI +4jANBgkqhkiG9w0BAQUFAAOCAQEAg8ev6n9NCjw5sWi+e22JLumzCecYV42Fmhfz +dkJQEw/HkG8zrcVJYCtsSVgZ1OK+t7+rSbyUyKu+KGwWaODIl0YgoGhnYIg5IFHY +aAERzqf2EQf27OysGh+yZm5WZ2B6dF7AbZc2rrUNXWZzwCUyRdhKBgePxLcHsU0G +DeGl6/R1yrqc0L2z0zIkTO5+4nYES0lT2PLpVDP85XEfPRRclkvxOvIAu2y0+pZV +CIgJwcyRGSmwIC3/yzikQOEXvnlhgP8HA4ZMTnsGnxGGjYnuJ8Tb4rwZjgvDwxPH +LQNjO9Po5KIqwoIIlBZU8O8fJ5AluA0OKBtHd0e9HKgl8ZS0Zg== +-----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=StartCom Certification Authority O=StartCom Ltd. OU=Secure Digital Certificate Signing +# Subject: CN=StartCom Certification Authority O=StartCom Ltd. OU=Secure Digital Certificate Signing +# Label: "StartCom Certification Authority" +# Serial: 45 +# MD5 Fingerprint: c9:3b:0d:84:41:fc:a4:76:79:23:08:57:de:10:19:16 +# SHA1 Fingerprint: a3:f1:33:3f:e2:42:bf:cf:c5:d1:4e:8f:39:42:98:40:68:10:d1:a0 +# SHA256 Fingerprint: e1:78:90:ee:09:a3:fb:f4:f4:8b:9c:41:4a:17:d6:37:b7:a5:06:47:e9:bc:75:23:22:72:7f:cc:17:42:a9:11 +-----BEGIN CERTIFICATE----- +MIIHhzCCBW+gAwIBAgIBLTANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQGEwJJTDEW +MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg +Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0NjM3WhcNMzYwOTE3MTk0NjM2WjB9 +MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMi +U2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3Rh +cnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZk +pMyONvg45iPwbm2xPN1yo4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rf +OQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/C +Ji/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/deMotHweXMAEtcnn6RtYT +Kqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt2PZE4XNi +HzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMM +Av+Z6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w ++2OqqGwaVLRcJXrJosmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+ +Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3 +Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVcUjyJthkqcwEKDwOzEmDyei+B +26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT37uMdBNSSwID +AQABo4ICEDCCAgwwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD +VR0OBBYEFE4L7xqkQFulF2mHMMo0aEPQQa7yMB8GA1UdIwQYMBaAFE4L7xqkQFul +F2mHMMo0aEPQQa7yMIIBWgYDVR0gBIIBUTCCAU0wggFJBgsrBgEEAYG1NwEBATCC +ATgwLgYIKwYBBQUHAgEWImh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL3BvbGljeS5w +ZGYwNAYIKwYBBQUHAgEWKGh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL2ludGVybWVk +aWF0ZS5wZGYwgc8GCCsGAQUFBwICMIHCMCcWIFN0YXJ0IENvbW1lcmNpYWwgKFN0 +YXJ0Q29tKSBMdGQuMAMCAQEagZZMaW1pdGVkIExpYWJpbGl0eSwgcmVhZCB0aGUg +c2VjdGlvbiAqTGVnYWwgTGltaXRhdGlvbnMqIG9mIHRoZSBTdGFydENvbSBDZXJ0 +aWZpY2F0aW9uIEF1dGhvcml0eSBQb2xpY3kgYXZhaWxhYmxlIGF0IGh0dHA6Ly93 +d3cuc3RhcnRzc2wuY29tL3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgG +CWCGSAGG+EIBDQQrFilTdGFydENvbSBGcmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1 +dGhvcml0eTANBgkqhkiG9w0BAQsFAAOCAgEAjo/n3JR5fPGFf59Jb2vKXfuM/gTF +wWLRfUKKvFO3lANmMD+x5wqnUCBVJX92ehQN6wQOQOY+2IirByeDqXWmN3PH/UvS +Ta0XQMhGvjt/UfzDtgUx3M2FIk5xt/JxXrAaxrqTi3iSSoX4eA+D/i+tLPfkpLst +0OcNOrg+zvZ49q5HJMqjNTbOx8aHmNrs++myziebiMMEofYLWWivydsQD032ZGNc +pRJvkrKTlMeIFw6Ttn5ii5B/q06f/ON1FE8qMt9bDeD1e5MNq6HPh+GlBEXoPBKl +CcWw0bdT82AUuoVpaiF8H3VhFyAXe2w7QSlc4axa0c2Mm+tgHRns9+Ww2vl5GKVF +P0lDV9LdJNUso/2RjSe15esUBppMeyG7Oq0wBhjA2MFrLH9ZXF2RsXAiV+uKa0hK +1Q8p7MZAwC+ITGgBF3f0JBlPvfrhsiAhS90a2Cl9qrjeVOwhVYBsHvUwyKMQ5bLm +KhQxw4UtjJixhlpPiVktucf3HMiKf8CdBUrmQk9io20ppB+Fq9vlgcitKj1MXVuE +JnHEhV5xJMqlG2zYYdMa4FTbzrqpMrUi9nNBCV24F10OD5mQ1kfabwo6YigUZ4LZ +8dCAWZvLMdibD4x3TrVoivJs9iQOLWxwxXPR3hTQcY+203sC9uO41Alua551hDnm +fyWl8kgAwKQB2j8= +-----END CERTIFICATE----- + +# Issuer: CN=StartCom Certification Authority G2 O=StartCom Ltd. +# Subject: CN=StartCom Certification Authority G2 O=StartCom Ltd. +# Label: "StartCom Certification Authority G2" +# Serial: 59 +# MD5 Fingerprint: 78:4b:fb:9e:64:82:0a:d3:b8:4c:62:f3:64:f2:90:64 +# SHA1 Fingerprint: 31:f1:fd:68:22:63:20:ee:c6:3b:3f:9d:ea:4a:3e:53:7c:7c:39:17 +# SHA256 Fingerprint: c7:ba:65:67:de:93:a7:98:ae:1f:aa:79:1e:71:2d:37:8f:ae:1f:93:c4:39:7f:ea:44:1b:b7:cb:e6:fd:59:95 +-----BEGIN CERTIFICATE----- +MIIFYzCCA0ugAwIBAgIBOzANBgkqhkiG9w0BAQsFADBTMQswCQYDVQQGEwJJTDEW +MBQGA1UEChMNU3RhcnRDb20gTHRkLjEsMCoGA1UEAxMjU3RhcnRDb20gQ2VydGlm +aWNhdGlvbiBBdXRob3JpdHkgRzIwHhcNMTAwMTAxMDEwMDAxWhcNMzkxMjMxMjM1 +OTAxWjBTMQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjEsMCoG +A1UEAxMjU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgRzIwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2iTZbB7cgNr2Cu+EWIAOVeq8Oo1XJ +JZlKxdBWQYeQTSFgpBSHO839sj60ZwNq7eEPS8CRhXBF4EKe3ikj1AENoBB5uNsD +vfOpL9HG4A/LnooUCri99lZi8cVytjIl2bLzvWXFDSxu1ZJvGIsAQRSCb0AgJnoo +D/Uefyf3lLE3PbfHkffiAez9lInhzG7TNtYKGXmu1zSCZf98Qru23QumNK9LYP5/ +Q0kGi4xDuFby2X8hQxfqp0iVAXV16iulQ5XqFYSdCI0mblWbq9zSOdIxHWDirMxW +RST1HFSr7obdljKF+ExP6JV2tgXdNiNnvP8V4so75qbsO+wmETRIjfaAKxojAuuK +HDp2KntWFhxyKrOq42ClAJ8Em+JvHhRYW6Vsi1g8w7pOOlz34ZYrPu8HvKTlXcxN +nw3h3Kq74W4a7I/htkxNeXJdFzULHdfBR9qWJODQcqhaX2YtENwvKhOuJv4KHBnM +0D4LnMgJLvlblnpHnOl68wVQdJVznjAJ85eCXuaPOQgeWeU1FEIT/wCc976qUM/i +UUjXuG+v+E5+M5iSFGI6dWPPe/regjupuznixL0sAA7IF6wT700ljtizkC+p2il9 +Ha90OrInwMEePnWjFqmveiJdnxMaz6eg6+OGCtP95paV1yPIN93EfKo2rJgaErHg +TuixO/XWb/Ew1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE +AwIBBjAdBgNVHQ4EFgQUS8W0QGutHLOlHGVuRjaJhwUMDrYwDQYJKoZIhvcNAQEL +BQADggIBAHNXPyzVlTJ+N9uWkusZXn5T50HsEbZH77Xe7XRcxfGOSeD8bpkTzZ+K +2s06Ctg6Wgk/XzTQLwPSZh0avZyQN8gMjgdalEVGKua+etqhqaRpEpKwfTbURIfX +UfEpY9Z1zRbkJ4kd+MIySP3bmdCPX1R0zKxnNBFi2QwKN4fRoxdIjtIXHfbX/dtl +6/2o1PXWT6RbdejF0mCy2wl+JYt7ulKSnj7oxXehPOBKc2thz4bcQ///If4jXSRK +9dNtD2IEBVeC2m6kMyV5Sy5UGYvMLD0w6dEG/+gyRr61M3Z3qAFdlsHB1b6uJcDJ +HgoJIIihDsnzb02CVAAgp9KP5DlUFy6NHrgbuxu9mk47EDTcnIhT76IxW1hPkWLI +wpqazRVdOKnWvvgTtZ8SafJQYqz7Fzf07rh1Z2AQ+4NQ+US1dZxAF7L+/XldblhY +XzD8AK6vM8EOTmy6p6ahfzLbOOCxchcKK5HsamMm7YnUeMx0HgX4a/6ManY5Ka5l +IxKVCCIcl85bBu4M4ru8H0ST9tg4RQUh7eStqxK2A6RCLi3ECToDZ2mEmuFZkIoo +hdVddLHRDiBYmxOlsGOm7XtH/UVVMKTumtTm4ofvmMkyghEpIrwACjFeLQ/Ajulr +so8uBtjRkcfGEvRM/TAXw8HaOFvjqermobp573PYtlNXLfbQ4ddI +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Global Root G2, OU=www.digicert.com, O=DigiCert Inc, C=US +# Subject: CN=DigiCert Global Root G2, OU=www.digicert.com, O=DigiCert Inc, C=US +# Serial: 33af1e6a711a9a0bb2864b11d09fae5 +# 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: /C=US/O=Internet Security Research Group/CN=ISRG Root X1 +# Subject: /C=US/O=Internet Security Research Group/CN=ISRG Root X1 +# Serial: 8210CFB0D240E3594463E0BB63828B00 +# 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: /C=US/O=Internet Security Research Group/CN=ISRG Root X2 +# Subject: /C=US/O=Internet Security Research Group/CN=ISRG Root X2 +# Serial: 41D29DD172EAEEA780C12C6CE92F8752 +# 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----- diff --git a/shotgun_api3/lib/httplib2/certs.py b/shotgun_api3/lib/httplib2/certs.py new file mode 100644 index 000000000..59d1ffc70 --- /dev/null +++ b/shotgun_api3/lib/httplib2/certs.py @@ -0,0 +1,42 @@ +"""Utilities for certificate management.""" + +import os + +certifi_available = False +certifi_where = None +try: + from certifi import where as certifi_where + certifi_available = True +except ImportError: + pass + +custom_ca_locater_available = False +custom_ca_locater_where = None +try: + from ca_certs_locater import get as custom_ca_locater_where + custom_ca_locater_available = True +except ImportError: + pass + + +BUILTIN_CA_CERTS = os.path.join( + os.path.dirname(os.path.abspath(__file__)), "cacerts.txt" +) + + +def where(): + env = os.environ.get("HTTPLIB2_CA_CERTS") + if env is not None: + if os.path.isfile(env): + return env + else: + raise RuntimeError("Environment variable HTTPLIB2_CA_CERTS not a valid file") + if custom_ca_locater_available: + return custom_ca_locater_where() + if certifi_available: + return certifi_where() + return BUILTIN_CA_CERTS + + +if __name__ == "__main__": + print(where()) diff --git a/shotgun_api3/lib/httplib2/error.py b/shotgun_api3/lib/httplib2/error.py new file mode 100644 index 000000000..0e68c12a8 --- /dev/null +++ b/shotgun_api3/lib/httplib2/error.py @@ -0,0 +1,48 @@ +# All exceptions raised here derive from HttpLib2Error +class HttpLib2Error(Exception): + pass + + +# Some exceptions can be caught and optionally +# be turned back into responses. +class HttpLib2ErrorWithResponse(HttpLib2Error): + def __init__(self, desc, response, content): + self.response = response + self.content = content + HttpLib2Error.__init__(self, desc) + + +class RedirectMissingLocation(HttpLib2ErrorWithResponse): + pass + + +class RedirectLimit(HttpLib2ErrorWithResponse): + pass + + +class FailedToDecompressContent(HttpLib2ErrorWithResponse): + pass + + +class UnimplementedDigestAuthOptionError(HttpLib2ErrorWithResponse): + pass + + +class UnimplementedHmacDigestAuthOptionError(HttpLib2ErrorWithResponse): + pass + + +class MalformedHeader(HttpLib2Error): + pass + + +class RelativeURIError(HttpLib2Error): + pass + + +class ServerNotFoundError(HttpLib2Error): + pass + + +class ProxiesUnavailableError(HttpLib2Error): + pass diff --git a/shotgun_api3/lib/httplib2/iri2uri.py b/shotgun_api3/lib/httplib2/iri2uri.py new file mode 100644 index 000000000..86e361e62 --- /dev/null +++ b/shotgun_api3/lib/httplib2/iri2uri.py @@ -0,0 +1,124 @@ +# -*- coding: utf-8 -*- +"""Converts an IRI to a URI.""" + +__author__ = "Joe Gregorio (joe@bitworking.org)" +__copyright__ = "Copyright 2006, Joe Gregorio" +__contributors__ = [] +__version__ = "1.0.0" +__license__ = "MIT" + +import urllib.parse + +# Convert an IRI to a URI following the rules in RFC 3987 +# +# The characters we need to enocde and escape are defined in the spec: +# +# iprivate = %xE000-F8FF / %xF0000-FFFFD / %x100000-10FFFD +# ucschar = %xA0-D7FF / %xF900-FDCF / %xFDF0-FFEF +# / %x10000-1FFFD / %x20000-2FFFD / %x30000-3FFFD +# / %x40000-4FFFD / %x50000-5FFFD / %x60000-6FFFD +# / %x70000-7FFFD / %x80000-8FFFD / %x90000-9FFFD +# / %xA0000-AFFFD / %xB0000-BFFFD / %xC0000-CFFFD +# / %xD0000-DFFFD / %xE1000-EFFFD + +escape_range = [ + (0xA0, 0xD7FF), + (0xE000, 0xF8FF), + (0xF900, 0xFDCF), + (0xFDF0, 0xFFEF), + (0x10000, 0x1FFFD), + (0x20000, 0x2FFFD), + (0x30000, 0x3FFFD), + (0x40000, 0x4FFFD), + (0x50000, 0x5FFFD), + (0x60000, 0x6FFFD), + (0x70000, 0x7FFFD), + (0x80000, 0x8FFFD), + (0x90000, 0x9FFFD), + (0xA0000, 0xAFFFD), + (0xB0000, 0xBFFFD), + (0xC0000, 0xCFFFD), + (0xD0000, 0xDFFFD), + (0xE1000, 0xEFFFD), + (0xF0000, 0xFFFFD), + (0x100000, 0x10FFFD), +] + + +def encode(c): + retval = c + i = ord(c) + for low, high in escape_range: + if i < low: + break + if i >= low and i <= high: + retval = "".join(["%%%2X" % o for o in c.encode("utf-8")]) + break + return retval + + +def iri2uri(uri): + """Convert an IRI to a URI. Note that IRIs must be + passed in a unicode strings. That is, do not utf-8 encode + the IRI before passing it into the function.""" + if isinstance(uri, str): + (scheme, authority, path, query, fragment) = urllib.parse.urlsplit(uri) + authority = authority.encode("idna").decode("utf-8") + # For each character in 'ucschar' or 'iprivate' + # 1. encode as utf-8 + # 2. then %-encode each octet of that utf-8 + uri = urllib.parse.urlunsplit((scheme, authority, path, query, fragment)) + uri = "".join([encode(c) for c in uri]) + return uri + + +if __name__ == "__main__": + import unittest + + class Test(unittest.TestCase): + def test_uris(self): + """Test that URIs are invariant under the transformation.""" + invariant = [ + "ftp://ftp.is.co.za/rfc/rfc1808.txt", + "http://www.ietf.org/rfc/rfc2396.txt", + "ldap://[2001:db8::7]/c=GB?objectClass?one", + "mailto:John.Doe@example.com", + "news:comp.infosystems.www.servers.unix", + "tel:+1-816-555-1212", + "telnet://192.0.2.16:80/", + "urn:oasis:names:specification:docbook:dtd:xml:4.1.2", + ] + for uri in invariant: + self.assertEqual(uri, iri2uri(uri)) + + def test_iri(self): + """Test that the right type of escaping is done for each part of the URI.""" + self.assertEqual( + "http://xn--o3h.com/%E2%98%84", + iri2uri("http://\N{COMET}.com/\N{COMET}"), + ) + self.assertEqual( + "http://bitworking.org/?fred=%E2%98%84", + iri2uri("http://bitworking.org/?fred=\N{COMET}"), + ) + self.assertEqual( + "http://bitworking.org/#%E2%98%84", + iri2uri("http://bitworking.org/#\N{COMET}"), + ) + self.assertEqual("#%E2%98%84", iri2uri("#\N{COMET}")) + self.assertEqual( + "/fred?bar=%E2%98%9A#%E2%98%84", + iri2uri("/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}"), + ) + self.assertEqual( + "/fred?bar=%E2%98%9A#%E2%98%84", + iri2uri(iri2uri("/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}")), + ) + self.assertNotEqual( + "/fred?bar=%E2%98%9A#%E2%98%84", + iri2uri( + "/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}".encode("utf-8") + ), + ) + + unittest.main() diff --git a/shotgun_api3/lib/httplib2/python2/__init__.py b/shotgun_api3/lib/httplib2/python2/__init__.py index 7f706602d..cbd8f382e 100644 --- a/shotgun_api3/lib/httplib2/python2/__init__.py +++ b/shotgun_api3/lib/httplib2/python2/__init__.py @@ -17,9 +17,10 @@ "Sam Ruby", "Louis Nyffenegger", "Alex Yu", + "Lai Han", ] __license__ = "MIT" -__version__ = "0.19.1" +__version__ = "0.22.0" import base64 import calendar @@ -467,7 +468,10 @@ def _decompressContent(response, new_content): if encoding == "gzip": content = gzip.GzipFile(fileobj=StringIO.StringIO(new_content)).read() if encoding == "deflate": - content = zlib.decompress(content, -zlib.MAX_WBITS) + try: + content = zlib.decompress(content, zlib.MAX_WBITS) + except (IOError, zlib.error): + content = zlib.decompress(content, -zlib.MAX_WBITS) response["content-length"] = str(len(content)) # Record the historical presence of the encoding in a way the won't interfere. response["-content-encoding"] = response["content-encoding"] @@ -961,34 +965,14 @@ def proxy_info_from_url(url, method="http", noproxy=None): """Construct a ProxyInfo from a URL (such as http_proxy env var) """ url = urlparse.urlparse(url) - username = None - password = None - port = None - if "@" in url[1]: - ident, host_port = url[1].split("@", 1) - if ":" in ident: - username, password = ident.split(":", 1) - else: - password = ident - else: - host_port = url[1] - if ":" in host_port: - host, port = host_port.split(":", 1) - else: - host = host_port - - if port: - port = int(port) - else: - port = dict(https=443, http=80)[method] proxy_type = 3 # socks.PROXY_TYPE_HTTP pi = ProxyInfo( proxy_type=proxy_type, - proxy_host=host, - proxy_port=port, - proxy_user=username or None, - proxy_pass=password or None, + proxy_host=url.hostname, + proxy_port=url.port or dict(https=443, http=80)[method], + proxy_user=url.username or None, + proxy_pass=url.password or None, proxy_headers=None, ) diff --git a/shotgun_api3/lib/httplib2/python2/cacerts.txt b/shotgun_api3/lib/httplib2/python2/cacerts.txt index a2a9833de..78a444c43 100644 --- a/shotgun_api3/lib/httplib2/python2/cacerts.txt +++ b/shotgun_api3/lib/httplib2/python2/cacerts.txt @@ -2138,34 +2138,6 @@ hdVddLHRDiBYmxOlsGOm7XtH/UVVMKTumtTm4ofvmMkyghEpIrwACjFeLQ/Ajulr so8uBtjRkcfGEvRM/TAXw8HaOFvjqermobp573PYtlNXLfbQ4ddI -----END CERTIFICATE----- -# Issuer: O=Digital Signature Trust Co., CN=DST Root CA X3 -# Subject: O=Digital Signature Trust Co., CN=DST Root CA X3 -# Label: "IdenTrust DST Root CA X3" -# Serial: 44AFB080D6A327BA893039862EF8406B -# MD5 Fingerprint: 41:03:52:DC:0F:F7:50:1B:16:F0:02:8E:BA:6F:45:C5 -# SHA1 Fingerprint: DA:C9:02:4F:54:D8:F6:DF:94:93:5F:B1:73:26:38:CA:6A:D7:7C:13 -# SHA256 Fingerprint: 06:87:26:03:31:A7:24:03:D9:09:F1:05:E6:9B:CF:0D:32:E1:BD:24:93:FF:C6:D9:20:6D:11:BC:D6:77:07:39 ------BEGIN CERTIFICATE----- -MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/ -MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT -DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow -PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD -Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB -AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O -rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq -OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b -xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw -7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD -aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV -HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG -SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69 -ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr -AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz -R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5 -JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo -Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ ------END CERTIFICATE----- - # Issuer: CN=DigiCert Global Root G2, OU=www.digicert.com, O=DigiCert Inc, C=US # Subject: CN=DigiCert Global Root G2, OU=www.digicert.com, O=DigiCert Inc, C=US # Serial: 33af1e6a711a9a0bb2864b11d09fae5 @@ -2194,3 +2166,60 @@ Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91 pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl MrY= -----END CERTIFICATE----- + +# Issuer: /C=US/O=Internet Security Research Group/CN=ISRG Root X1 +# Subject: /C=US/O=Internet Security Research Group/CN=ISRG Root X1 +# Serial: 8210CFB0D240E3594463E0BB63828B00 +# 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: /C=US/O=Internet Security Research Group/CN=ISRG Root X2 +# Subject: /C=US/O=Internet Security Research Group/CN=ISRG Root X2 +# Serial: 41D29DD172EAEEA780C12C6CE92F8752 +# 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----- diff --git a/shotgun_api3/lib/httplib2/python3/__init__.py b/shotgun_api3/lib/httplib2/python3/__init__.py index 9a363f698..ba5fa2f23 100644 --- a/shotgun_api3/lib/httplib2/python3/__init__.py +++ b/shotgun_api3/lib/httplib2/python3/__init__.py @@ -13,9 +13,10 @@ "Louis Nyffenegger", "Mark Pilgrim", "Alex Yu", + "Lai Han", ] __license__ = "MIT" -__version__ = "0.19.1" +__version__ = "0.22.0" import base64 import calendar @@ -128,8 +129,14 @@ def has_timeout(timeout): # Both PROTOCOL_TLS and PROTOCOL_SSLv23 are equivalent and means: # > Selects the highest protocol version that both the client and server support. # > Despite the name, this option can select “TLS” protocols as well as “SSL”. -# source: https://docs.python.org/3.5/library/ssl.html#ssl.PROTOCOL_TLS -DEFAULT_TLS_VERSION = getattr(ssl, "PROTOCOL_TLS", None) or getattr(ssl, "PROTOCOL_SSLv23") +# source: https://docs.python.org/3.5/library/ssl.html#ssl.PROTOCOL_SSLv23 + +# PROTOCOL_TLS_CLIENT is python 3.10.0+. PROTOCOL_TLS is deprecated. +# > Auto-negotiate the highest protocol version that both the client and server support, and configure the context client-side connections. +# > The protocol enables CERT_REQUIRED and check_hostname by default. +# source: https://docs.python.org/3.10/library/ssl.html#ssl.PROTOCOL_TLS + +DEFAULT_TLS_VERSION = getattr(ssl, "PROTOCOL_TLS_CLIENT", None) or getattr(ssl, "PROTOCOL_TLS", None) or getattr(ssl, "PROTOCOL_SSLv23") def _build_ssl_context( @@ -145,21 +152,28 @@ def _build_ssl_context( raise RuntimeError("httplib2 requires Python 3.2+ for ssl.SSLContext") context = ssl.SSLContext(DEFAULT_TLS_VERSION) + # check_hostname and verify_mode should be set in opposite order during disable + # https://bugs.python.org/issue31431 + if disable_ssl_certificate_validation and hasattr(context, "check_hostname"): + context.check_hostname = not disable_ssl_certificate_validation context.verify_mode = ssl.CERT_NONE if disable_ssl_certificate_validation else ssl.CERT_REQUIRED # SSLContext.maximum_version and SSLContext.minimum_version are python 3.7+. # source: https://docs.python.org/3/library/ssl.html#ssl.SSLContext.maximum_version if maximum_version is not None: if hasattr(context, "maximum_version"): - context.maximum_version = getattr(ssl.TLSVersion, maximum_version) + if isinstance(maximum_version, str): + maximum_version = getattr(ssl.TLSVersion, maximum_version) + context.maximum_version = maximum_version else: raise RuntimeError("setting tls_maximum_version requires Python 3.7 and OpenSSL 1.1 or newer") if minimum_version is not None: if hasattr(context, "minimum_version"): - context.minimum_version = getattr(ssl.TLSVersion, minimum_version) + if isinstance(minimum_version, str): + minimum_version = getattr(ssl.TLSVersion, minimum_version) + context.minimum_version = minimum_version else: raise RuntimeError("setting tls_minimum_version requires Python 3.7 and OpenSSL 1.1 or newer") - # check_hostname requires python 3.4+ # we will perform the equivalent in HTTPSConnectionWithTimeout.connect() by calling ssl.match_hostname # if check_hostname is not supported. @@ -180,6 +194,29 @@ def _get_end2end_headers(response): return [header for header in list(response.keys()) if header not in hopbyhop] +_missing = object() + + +def _errno_from_exception(e): + # TODO python 3.11+ cheap try: return e.errno except AttributeError: pass + errno = getattr(e, "errno", _missing) + if errno is not _missing: + return errno + + # socket.error and common wrap in .args + args = getattr(e, "args", None) + if args: + return _errno_from_exception(args[0]) + + # pysocks.ProxyError wraps in .socket_err + # https://github.com/httplib2/httplib2/pull/202 + socket_err = getattr(e, "socket_err", None) + if socket_err: + return _errno_from_exception(socket_err) + + return None + + URI = re.compile(r"^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?") @@ -357,7 +394,10 @@ def _decompressContent(response, new_content): if encoding == "gzip": content = gzip.GzipFile(fileobj=io.BytesIO(new_content)).read() if encoding == "deflate": - content = zlib.decompress(content, -zlib.MAX_WBITS) + try: + content = zlib.decompress(content, zlib.MAX_WBITS) + except (IOError, zlib.error): + content = zlib.decompress(content, -zlib.MAX_WBITS) response["content-length"] = str(len(content)) # Record the historical presence of the encoding in a way the won't interfere. response["-content-encoding"] = response["content-encoding"] @@ -913,34 +953,14 @@ def proxy_info_from_url(url, method="http", noproxy=None): """Construct a ProxyInfo from a URL (such as http_proxy env var) """ url = urllib.parse.urlparse(url) - username = None - password = None - port = None - if "@" in url[1]: - ident, host_port = url[1].split("@", 1) - if ":" in ident: - username, password = ident.split(":", 1) - else: - password = ident - else: - host_port = url[1] - if ":" in host_port: - host, port = host_port.split(":", 1) - else: - host = host_port - - if port: - port = int(port) - else: - port = dict(https=443, http=80)[method] proxy_type = 3 # socks.PROXY_TYPE_HTTP pi = ProxyInfo( proxy_type=proxy_type, - proxy_host=host, - proxy_port=port, - proxy_user=username or None, - proxy_pass=password or None, + proxy_host=url.hostname, + proxy_port=url.port or dict(https=443, http=80)[method], + proxy_user=url.username or None, + proxy_pass=url.password or None, proxy_headers=None, ) @@ -1238,7 +1258,7 @@ def __init__( tls_maximum_version / tls_minimum_version require Python 3.7+ / OpenSSL 1.1.0g+. A value of "TLSv1_3" requires OpenSSL 1.1.1+. -""" + """ self.proxy_info = proxy_info self.ca_certs = ca_certs self.disable_ssl_certificate_validation = disable_ssl_certificate_validation @@ -1352,7 +1372,7 @@ def _conn_request(self, conn, request_uri, method, body, headers): conn.close() raise ServerNotFoundError("Unable to find the server at %s" % conn.host) except socket.error as e: - errno_ = e.args[0].errno if isinstance(e.args[0], socket.error) else e.errno + errno_ = _errno_from_exception(e) if errno_ in (errno.ENETUNREACH, errno.EADDRNOTAVAIL) and i < RETRIES: continue # retry on potentially transient errors raise @@ -1657,12 +1677,8 @@ def request( entry_disposition = _entry_disposition(info, headers) if entry_disposition == "FRESH": - if not cached_value: - info["status"] = "504" - content = b"" response = Response(info) - if cached_value: - response.fromcache = True + response.fromcache = True return (response, content) if entry_disposition == "STALE": diff --git a/shotgun_api3/lib/httplib2/python3/auth.py b/shotgun_api3/lib/httplib2/python3/auth.py index 7a1c2a7e8..53f427be1 100644 --- a/shotgun_api3/lib/httplib2/python3/auth.py +++ b/shotgun_api3/lib/httplib2/python3/auth.py @@ -5,6 +5,12 @@ from .error import * + +try: # pyparsing>=3.0.0 + downcaseTokens = pp.common.downcaseTokens +except AttributeError: + downcaseTokens = pp.downcaseTokens + UNQUOTE_PAIRS = re.compile(r"\\(.)") unquote = lambda s, l, t: UNQUOTE_PAIRS.sub(r"\1", t[0][1:-1]) @@ -17,7 +23,7 @@ ) quoted_string = pp.dblQuotedString.copy().setName("quoted-string").setParseAction(unquote) -auth_param_name = token.copy().setName("auth-param-name").addParseAction(pp.downcaseTokens) +auth_param_name = token.copy().setName("auth-param-name").addParseAction(downcaseTokens) auth_param = auth_param_name + pp.Suppress("=") + (quoted_string | token) params = pp.Dict(pp.delimitedList(pp.Group(auth_param))) diff --git a/shotgun_api3/lib/httplib2/python3/cacerts.txt b/shotgun_api3/lib/httplib2/python3/cacerts.txt index 8020c1b4d..78a444c43 100644 --- a/shotgun_api3/lib/httplib2/python3/cacerts.txt +++ b/shotgun_api3/lib/httplib2/python3/cacerts.txt @@ -2138,34 +2138,6 @@ hdVddLHRDiBYmxOlsGOm7XtH/UVVMKTumtTm4ofvmMkyghEpIrwACjFeLQ/Ajulr so8uBtjRkcfGEvRM/TAXw8HaOFvjqermobp573PYtlNXLfbQ4ddI -----END CERTIFICATE----- -# Issuer: O=Digital Signature Trust Co., CN=DST Root CA X3 -# Subject: O=Digital Signature Trust Co., CN=DST Root CA X3 -# Label: "IdenTrust DST Root CA X3" -# Serial: 44AFB080D6A327BA893039862EF8406B -# MD5 Fingerprint: 41:03:52:DC:0F:F7:50:1B:16:F0:02:8E:BA:6F:45:C5 -# SHA1 Fingerprint: DA:C9:02:4F:54:D8:F6:DF:94:93:5F:B1:73:26:38:CA:6A:D7:7C:13 -# SHA256 Fingerprint: 06:87:26:03:31:A7:24:03:D9:09:F1:05:E6:9B:CF:0D:32:E1:BD:24:93:FF:C6:D9:20:6D:11:BC:D6:77:07:39 ------BEGIN CERTIFICATE----- -MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/ -MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT -DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow -PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD -Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB -AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O -rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq -OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b -xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw -7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD -aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV -HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG -SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69 -ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr -AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz -R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5 -JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo -Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ ------END CERTIFICATE----- - # Issuer: CN=DigiCert Global Root G2, OU=www.digicert.com, O=DigiCert Inc, C=US # Subject: CN=DigiCert Global Root G2, OU=www.digicert.com, O=DigiCert Inc, C=US # Serial: 33af1e6a711a9a0bb2864b11d09fae5 @@ -2195,3 +2167,59 @@ pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl MrY= -----END CERTIFICATE----- +# Issuer: /C=US/O=Internet Security Research Group/CN=ISRG Root X1 +# Subject: /C=US/O=Internet Security Research Group/CN=ISRG Root X1 +# Serial: 8210CFB0D240E3594463E0BB63828B00 +# 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: /C=US/O=Internet Security Research Group/CN=ISRG Root X2 +# Subject: /C=US/O=Internet Security Research Group/CN=ISRG Root X2 +# Serial: 41D29DD172EAEEA780C12C6CE92F8752 +# 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----- diff --git a/shotgun_api3/lib/httplib2/socks.py b/shotgun_api3/lib/httplib2/socks.py new file mode 100644 index 000000000..cc68e634c --- /dev/null +++ b/shotgun_api3/lib/httplib2/socks.py @@ -0,0 +1,518 @@ +"""SocksiPy - Python SOCKS module. + +Version 1.00 + +Copyright 2006 Dan-Haim. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +3. Neither the name of Dan Haim nor the names of his contributors may be used + to endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE. + +This module provides a standard socket-like interface for Python +for tunneling connections through SOCKS proxies. + +Minor modifications made by Christopher Gilbert (http://motomastyle.com/) for +use in PyLoris (http://pyloris.sourceforge.net/). + +Minor modifications made by Mario Vilas (http://breakingcode.wordpress.com/) +mainly to merge bug fixes found in Sourceforge. +""" + +import base64 +import socket +import struct +import sys + +if getattr(socket, "socket", None) is None: + raise ImportError("socket.socket missing, proxy support unusable") + +PROXY_TYPE_SOCKS4 = 1 +PROXY_TYPE_SOCKS5 = 2 +PROXY_TYPE_HTTP = 3 +PROXY_TYPE_HTTP_NO_TUNNEL = 4 + +_defaultproxy = None +_orgsocket = socket.socket + + +class ProxyError(Exception): + pass + + +class GeneralProxyError(ProxyError): + pass + + +class Socks5AuthError(ProxyError): + pass + + +class Socks5Error(ProxyError): + pass + + +class Socks4Error(ProxyError): + pass + + +class HTTPError(ProxyError): + pass + + +_generalerrors = ( + "success", + "invalid data", + "not connected", + "not available", + "bad proxy type", + "bad input", +) + +_socks5errors = ( + "succeeded", + "general SOCKS server failure", + "connection not allowed by ruleset", + "Network unreachable", + "Host unreachable", + "Connection refused", + "TTL expired", + "Command not supported", + "Address type not supported", + "Unknown error", +) + +_socks5autherrors = ( + "succeeded", + "authentication is required", + "all offered authentication methods were rejected", + "unknown username or invalid password", + "unknown error", +) + +_socks4errors = ( + "request granted", + "request rejected or failed", + "request rejected because SOCKS server cannot connect to identd on the client", + "request rejected because the client program and identd report different " + "user-ids", + "unknown error", +) + + +def setdefaultproxy( + proxytype=None, addr=None, port=None, rdns=True, username=None, password=None +): + """setdefaultproxy(proxytype, addr[, port[, rdns[, username[, password]]]]) + Sets a default proxy which all further socksocket objects will use, + unless explicitly changed. + """ + global _defaultproxy + _defaultproxy = (proxytype, addr, port, rdns, username, password) + + +def wrapmodule(module): + """wrapmodule(module) + + Attempts to replace a module's socket library with a SOCKS socket. Must set + a default proxy using setdefaultproxy(...) first. + This will only work on modules that import socket directly into the + namespace; + most of the Python Standard Library falls into this category. + """ + if _defaultproxy != None: + module.socket.socket = socksocket + else: + raise GeneralProxyError((4, "no proxy specified")) + + +class socksocket(socket.socket): + """socksocket([family[, type[, proto]]]) -> socket object + Open a SOCKS enabled socket. The parameters are the same as + those of the standard socket init. In order for SOCKS to work, + you must specify family=AF_INET, type=SOCK_STREAM and proto=0. + """ + + def __init__( + self, family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0, _sock=None + ): + _orgsocket.__init__(self, family, type, proto, _sock) + if _defaultproxy != None: + self.__proxy = _defaultproxy + else: + self.__proxy = (None, None, None, None, None, None) + self.__proxysockname = None + self.__proxypeername = None + self.__httptunnel = True + + def __recvall(self, count): + """__recvall(count) -> data + Receive EXACTLY the number of bytes requested from the socket. + Blocks until the required number of bytes have been received. + """ + data = self.recv(count) + while len(data) < count: + d = self.recv(count - len(data)) + if not d: + raise GeneralProxyError((0, "connection closed unexpectedly")) + data = data + d + return data + + def sendall(self, content, *args): + """ override socket.socket.sendall method to rewrite the header + for non-tunneling proxies if needed + """ + if not self.__httptunnel: + content = self.__rewriteproxy(content) + return super(socksocket, self).sendall(content, *args) + + def __rewriteproxy(self, header): + """ rewrite HTTP request headers to support non-tunneling proxies + (i.e. those which do not support the CONNECT method). + This only works for HTTP (not HTTPS) since HTTPS requires tunneling. + """ + host, endpt = None, None + hdrs = header.split("\r\n") + for hdr in hdrs: + if hdr.lower().startswith("host:"): + host = hdr + elif hdr.lower().startswith("get") or hdr.lower().startswith("post"): + endpt = hdr + if host and endpt: + hdrs.remove(host) + hdrs.remove(endpt) + host = host.split(" ")[1] + endpt = endpt.split(" ") + if self.__proxy[4] != None and self.__proxy[5] != None: + hdrs.insert(0, self.__getauthheader()) + hdrs.insert(0, "Host: %s" % host) + hdrs.insert(0, "%s http://%s%s %s" % (endpt[0], host, endpt[1], endpt[2])) + return "\r\n".join(hdrs) + + def __getauthheader(self): + auth = self.__proxy[4] + b":" + self.__proxy[5] + return "Proxy-Authorization: Basic " + base64.b64encode(auth).decode() + + def setproxy( + self, + proxytype=None, + addr=None, + port=None, + rdns=True, + username=None, + password=None, + headers=None, + ): + """setproxy(proxytype, addr[, port[, rdns[, username[, password]]]]) + + Sets the proxy to be used. + proxytype - The type of the proxy to be used. Three types + are supported: PROXY_TYPE_SOCKS4 (including socks4a), + PROXY_TYPE_SOCKS5 and PROXY_TYPE_HTTP + addr - The address of the server (IP or DNS). + port - The port of the server. Defaults to 1080 for SOCKS + servers and 8080 for HTTP proxy servers. + rdns - Should DNS queries be preformed on the remote side + (rather than the local side). The default is True. + Note: This has no effect with SOCKS4 servers. + username - Username to authenticate with to the server. + The default is no authentication. + password - Password to authenticate with to the server. + Only relevant when username is also provided. + headers - Additional or modified headers for the proxy connect + request. + """ + self.__proxy = ( + proxytype, + addr, + port, + rdns, + username.encode() if username else None, + password.encode() if password else None, + headers, + ) + + def __negotiatesocks5(self, destaddr, destport): + """__negotiatesocks5(self,destaddr,destport) + Negotiates a connection through a SOCKS5 server. + """ + # First we'll send the authentication packages we support. + if (self.__proxy[4] != None) and (self.__proxy[5] != None): + # The username/password details were supplied to the + # setproxy method so we support the USERNAME/PASSWORD + # authentication (in addition to the standard none). + self.sendall(struct.pack("BBBB", 0x05, 0x02, 0x00, 0x02)) + else: + # No username/password were entered, therefore we + # only support connections with no authentication. + self.sendall(struct.pack("BBB", 0x05, 0x01, 0x00)) + # We'll receive the server's response to determine which + # method was selected + chosenauth = self.__recvall(2) + if chosenauth[0:1] != chr(0x05).encode(): + self.close() + raise GeneralProxyError((1, _generalerrors[1])) + # Check the chosen authentication method + if chosenauth[1:2] == chr(0x00).encode(): + # No authentication is required + pass + elif chosenauth[1:2] == chr(0x02).encode(): + # Okay, we need to perform a basic username/password + # authentication. + packet = bytearray() + packet.append(0x01) + packet.append(len(self.__proxy[4])) + packet.extend(self.__proxy[4]) + packet.append(len(self.__proxy[5])) + packet.extend(self.__proxy[5]) + self.sendall(packet) + authstat = self.__recvall(2) + if authstat[0:1] != chr(0x01).encode(): + # Bad response + self.close() + raise GeneralProxyError((1, _generalerrors[1])) + if authstat[1:2] != chr(0x00).encode(): + # Authentication failed + self.close() + raise Socks5AuthError((3, _socks5autherrors[3])) + # Authentication succeeded + else: + # Reaching here is always bad + self.close() + if chosenauth[1] == chr(0xFF).encode(): + raise Socks5AuthError((2, _socks5autherrors[2])) + else: + raise GeneralProxyError((1, _generalerrors[1])) + # Now we can request the actual connection + req = struct.pack("BBB", 0x05, 0x01, 0x00) + # If the given destination address is an IP address, we'll + # use the IPv4 address request even if remote resolving was specified. + try: + ipaddr = socket.inet_aton(destaddr) + req = req + chr(0x01).encode() + ipaddr + except socket.error: + # Well it's not an IP number, so it's probably a DNS name. + if self.__proxy[3]: + # Resolve remotely + ipaddr = None + req = ( + req + + chr(0x03).encode() + + chr(len(destaddr)).encode() + + destaddr.encode() + ) + else: + # Resolve locally + ipaddr = socket.inet_aton(socket.gethostbyname(destaddr)) + req = req + chr(0x01).encode() + ipaddr + req = req + struct.pack(">H", destport) + self.sendall(req) + # Get the response + resp = self.__recvall(4) + if resp[0:1] != chr(0x05).encode(): + self.close() + raise GeneralProxyError((1, _generalerrors[1])) + elif resp[1:2] != chr(0x00).encode(): + # Connection failed + self.close() + if ord(resp[1:2]) <= 8: + raise Socks5Error((ord(resp[1:2]), _socks5errors[ord(resp[1:2])])) + else: + raise Socks5Error((9, _socks5errors[9])) + # Get the bound address/port + elif resp[3:4] == chr(0x01).encode(): + boundaddr = self.__recvall(4) + elif resp[3:4] == chr(0x03).encode(): + resp = resp + self.recv(1) + boundaddr = self.__recvall(ord(resp[4:5])) + else: + self.close() + raise GeneralProxyError((1, _generalerrors[1])) + boundport = struct.unpack(">H", self.__recvall(2))[0] + self.__proxysockname = (boundaddr, boundport) + if ipaddr != None: + self.__proxypeername = (socket.inet_ntoa(ipaddr), destport) + else: + self.__proxypeername = (destaddr, destport) + + def getproxysockname(self): + """getsockname() -> address info + Returns the bound IP address and port number at the proxy. + """ + return self.__proxysockname + + def getproxypeername(self): + """getproxypeername() -> address info + Returns the IP and port number of the proxy. + """ + return _orgsocket.getpeername(self) + + def getpeername(self): + """getpeername() -> address info + Returns the IP address and port number of the destination + machine (note: getproxypeername returns the proxy) + """ + return self.__proxypeername + + def __negotiatesocks4(self, destaddr, destport): + """__negotiatesocks4(self,destaddr,destport) + Negotiates a connection through a SOCKS4 server. + """ + # Check if the destination address provided is an IP address + rmtrslv = False + try: + ipaddr = socket.inet_aton(destaddr) + except socket.error: + # It's a DNS name. Check where it should be resolved. + if self.__proxy[3]: + ipaddr = struct.pack("BBBB", 0x00, 0x00, 0x00, 0x01) + rmtrslv = True + else: + ipaddr = socket.inet_aton(socket.gethostbyname(destaddr)) + # Construct the request packet + req = struct.pack(">BBH", 0x04, 0x01, destport) + ipaddr + # The username parameter is considered userid for SOCKS4 + if self.__proxy[4] != None: + req = req + self.__proxy[4] + req = req + chr(0x00).encode() + # DNS name if remote resolving is required + # NOTE: This is actually an extension to the SOCKS4 protocol + # called SOCKS4A and may not be supported in all cases. + if rmtrslv: + req = req + destaddr + chr(0x00).encode() + self.sendall(req) + # Get the response from the server + resp = self.__recvall(8) + if resp[0:1] != chr(0x00).encode(): + # Bad data + self.close() + raise GeneralProxyError((1, _generalerrors[1])) + if resp[1:2] != chr(0x5A).encode(): + # Server returned an error + self.close() + if ord(resp[1:2]) in (91, 92, 93): + self.close() + raise Socks4Error((ord(resp[1:2]), _socks4errors[ord(resp[1:2]) - 90])) + else: + raise Socks4Error((94, _socks4errors[4])) + # Get the bound address/port + self.__proxysockname = ( + socket.inet_ntoa(resp[4:]), + struct.unpack(">H", resp[2:4])[0], + ) + if rmtrslv != None: + self.__proxypeername = (socket.inet_ntoa(ipaddr), destport) + else: + self.__proxypeername = (destaddr, destport) + + def __negotiatehttp(self, destaddr, destport): + """__negotiatehttp(self,destaddr,destport) + Negotiates a connection through an HTTP server. + """ + # If we need to resolve locally, we do this now + if not self.__proxy[3]: + addr = socket.gethostbyname(destaddr) + else: + addr = destaddr + headers = ["CONNECT ", addr, ":", str(destport), " HTTP/1.1\r\n"] + wrote_host_header = False + wrote_auth_header = False + if self.__proxy[6] != None: + for key, val in self.__proxy[6].iteritems(): + headers += [key, ": ", val, "\r\n"] + wrote_host_header = key.lower() == "host" + wrote_auth_header = key.lower() == "proxy-authorization" + if not wrote_host_header: + headers += ["Host: ", destaddr, "\r\n"] + if not wrote_auth_header: + if self.__proxy[4] != None and self.__proxy[5] != None: + headers += [self.__getauthheader(), "\r\n"] + headers.append("\r\n") + self.sendall("".join(headers).encode()) + # We read the response until we get the string "\r\n\r\n" + resp = self.recv(1) + while resp.find("\r\n\r\n".encode()) == -1: + resp = resp + self.recv(1) + # We just need the first line to check if the connection + # was successful + statusline = resp.splitlines()[0].split(" ".encode(), 2) + if statusline[0] not in ("HTTP/1.0".encode(), "HTTP/1.1".encode()): + self.close() + raise GeneralProxyError((1, _generalerrors[1])) + try: + statuscode = int(statusline[1]) + except ValueError: + self.close() + raise GeneralProxyError((1, _generalerrors[1])) + if statuscode != 200: + self.close() + raise HTTPError((statuscode, statusline[2])) + self.__proxysockname = ("0.0.0.0", 0) + self.__proxypeername = (addr, destport) + + def connect(self, destpair): + """connect(self, despair) + Connects to the specified destination through a proxy. + destpar - A tuple of the IP/DNS address and the port number. + (identical to socket's connect). + To select the proxy server use setproxy(). + """ + # Do a minimal input check first + if ( + (not type(destpair) in (list, tuple)) + or (len(destpair) < 2) + or (not isinstance(destpair[0], (str, bytes))) + or (type(destpair[1]) != int) + ): + raise GeneralProxyError((5, _generalerrors[5])) + if self.__proxy[0] == PROXY_TYPE_SOCKS5: + if self.__proxy[2] != None: + portnum = self.__proxy[2] + else: + portnum = 1080 + _orgsocket.connect(self, (self.__proxy[1], portnum)) + self.__negotiatesocks5(destpair[0], destpair[1]) + elif self.__proxy[0] == PROXY_TYPE_SOCKS4: + if self.__proxy[2] != None: + portnum = self.__proxy[2] + else: + portnum = 1080 + _orgsocket.connect(self, (self.__proxy[1], portnum)) + self.__negotiatesocks4(destpair[0], destpair[1]) + elif self.__proxy[0] == PROXY_TYPE_HTTP: + if self.__proxy[2] != None: + portnum = self.__proxy[2] + else: + portnum = 8080 + _orgsocket.connect(self, (self.__proxy[1], portnum)) + self.__negotiatehttp(destpair[0], destpair[1]) + elif self.__proxy[0] == PROXY_TYPE_HTTP_NO_TUNNEL: + if self.__proxy[2] != None: + portnum = self.__proxy[2] + else: + portnum = 8080 + _orgsocket.connect(self, (self.__proxy[1], portnum)) + if destpair[1] == 443: + self.__negotiatehttp(destpair[0], destpair[1]) + else: + self.__httptunnel = False + elif self.__proxy[0] == None: + _orgsocket.connect(self, (destpair[0], destpair[1])) + else: + raise GeneralProxyError((4, _generalerrors[4])) diff --git a/shotgun_api3/lib/requirements.txt b/shotgun_api3/lib/requirements.txt index 08bf3f4b7..cf47abb45 100644 --- a/shotgun_api3/lib/requirements.txt +++ b/shotgun_api3/lib/requirements.txt @@ -28,7 +28,7 @@ # This file is unused. It is left there so Github can warn us is a CVE is # released for our dependencies. -httplib2==0.19.1 +httplib2==0.22.0 six==1.13.0 certifi==2023.7.22 pyparsing==2.4.7 \ No newline at end of file From 28bca032158c02dad22617d3bc679cc48cf33078 Mon Sep 17 00:00:00 2001 From: Julien Langlois <16244608+julien-lang@users.noreply.github.com> Date: Tue, 19 Sep 2023 17:19:26 -0700 Subject: [PATCH 027/125] SG-32675 Update licensing (#300) * Alpha order * Add missing pyparsing license * Update certifi license block --- software_credits | 77 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 52 insertions(+), 25 deletions(-) diff --git a/software_credits b/software_credits index dd1ef31da..0c70a930a 100644 --- a/software_credits +++ b/software_credits @@ -44,33 +44,16 @@ The ShotGrid Python API uses the following software. Thanks to their creators, l 8. By copying, installing or otherwise using Python 2.7.17, Licensee agrees to be bound by the terms and conditions of this License Agreement. -============================== SIX ============================== - -Copyright (c) 2010-2020 Benjamin Peterson - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ============================== Certifi ============================== -Certifi is licensed under the Mozilla Public License v.2.0, -which can be found at http://www.mozilla.org/MPL/2.0/. -A text copy of this license and the source code for Certifi 2020.06.20 (and modifications made by Autodesk, if any) -can be found at the Autodesk website www.autodesk.com/lgplsource. +This Autodesk software contains the unmodified python-certifi package. +The use and distribution terms for this software are covered by the Mozilla +Public License 2.0 (https://www.mozilla.org/en-US/MPL/2.0/). +By using this software in any fashion, you are agreeing to be bound by the terms +of this license. +The source code for python-certifi is available from https://github.com/certifi/python-certifi. + ============================== Httplib2 ============================== Httplib2 Software License @@ -95,4 +78,48 @@ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. + + +============================== pyparsing ============================== + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +============================== SIX ============================== + +Copyright (c) 2010-2019 Benjamin Peterson + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. From cad47354b0a88f8e0914496231e6e5a71a5be699 Mon Sep 17 00:00:00 2001 From: Shayna Duguid Date: Wed, 20 Sep 2023 14:46:17 -0400 Subject: [PATCH 028/125] SG-30321 - updates to links (#299) --- docs/reference.rst | 2 +- setup.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/reference.rst b/docs/reference.rst index 90c523161..e6f705ede 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -919,7 +919,7 @@ usage. This **does not** mean your ShotGrid server performance will suffer in general, just any pages that are specifically displaying EventLogEntries in the web application, or API queries on the event log that are run. We are always looking for ways to improve this in the future. If you have any -immediate concerns, please `reach out to our support team `_ +immediate concerns, please `reach out to our support team `_ ********************* Environment Variables diff --git a/setup.py b/setup.py index 37b56dbae..12e5e6209 100644 --- a/setup.py +++ b/setup.py @@ -30,8 +30,8 @@ version='3.3.6', description='Shotgun Python API ', long_description=readme, - author='Shotgun Software', - author_email='https://developer.shotgridsoftware.com', + author='ShotGrid Software', + author_email='https://www.autodesk.com/support/contact-support', url='https://github.com/shotgunsoftware/python-api', license=license, packages=find_packages(exclude=('tests',)), From aa6a948c43e63b13e43993586ae0b3ecd7c3af7f Mon Sep 17 00:00:00 2001 From: Julien Langlois <16244608+julien-lang@users.noreply.github.com> Date: Thu, 21 Sep 2023 09:59:29 -0700 Subject: [PATCH 029/125] SG-31925 Fixup follow flaky tests (#304) Fix race condition --- tests/base.py | 27 ++++++++++ tests/test_api.py | 131 +++++++++++++++++++++++++++------------------- 2 files changed, 104 insertions(+), 54 deletions(-) diff --git a/tests/base.py b/tests/base.py index 6793b1e40..8296061a1 100644 --- a/tests/base.py +++ b/tests/base.py @@ -1,5 +1,7 @@ """Base class for ShotGrid API tests.""" +import contextlib import os +import random import re import unittest @@ -339,6 +341,31 @@ def _setup_db(cls, config, sg): 'linux_path': 'nowhere'} cls.local_storage = _find_or_create_entity(sg, 'LocalStorage', data, keys) + @contextlib.contextmanager + def gen_entity(self, entity_type, **kwargs): + # Helper creator + if entity_type == "HumanUser": + if "login" not in kwargs: + kwargs["login"] = "test-python-api-{rnd}" + + if "sg_status_list" not in kwargs: + kwargs["sg_status_list"] = "dis" + + if "password_proxy" not in kwargs: + kwargs["password_proxy"] = self.config.human_password + + item_rnd = random.randrange(100,999) + for k in kwargs: + if isinstance(kwargs[k], str): + kwargs[k] = kwargs[k].format(rnd=item_rnd) + + entity = self.sg.create(entity_type, kwargs, return_fields=list(kwargs.keys())) + try: + yield entity + finally: + rv = self.sg.delete(entity_type, entity["id"]) + assert rv == True + class HumanUserAuthLiveTestBase(LiveTestBase): ''' diff --git a/tests/test_api.py b/tests/test_api.py index f4bb1eb26..fbb74eebc 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1694,15 +1694,6 @@ def test_include_archived_projects(self): class TestFollow(base.LiveTestBase): - def setUp(self): - super(TestFollow, self).setUp() - self.sg.update('HumanUser', self.human_user['id'], {'projects': [self.project]}) - - # As the Follow entity isn't exposed directly, we clear out existing - # follows for the user before running our tests. - if self.sg.server_caps.version and self.sg.server_caps.version >= (7, 0, 12): - for entity in self.sg.following(self.human_user): - self.sg.unfollow(self.human_user, entity) def test_follow_unfollow(self): '''Test follow method''' @@ -1710,11 +1701,18 @@ def test_follow_unfollow(self): if not self.sg.server_caps.version or self.sg.server_caps.version < (5, 1, 22): return - result = self.sg.follow(self.human_user, self.shot) - assert(result['followed']) + with self.gen_entity( + "HumanUser", + projects=[self.project], + ) as human_user, self.gen_entity( + "Shot", + project=self.project, + ) as shot: + result = self.sg.follow(human_user, shot) + assert(result['followed']) - result = self.sg.unfollow(self.human_user, self.shot) - assert(result['unfollowed']) + result = self.sg.unfollow(human_user, shot) + assert(result['unfollowed']) def test_followers(self): '''Test followers method''' @@ -1722,12 +1720,20 @@ def test_followers(self): if not self.sg.server_caps.version or self.sg.server_caps.version < (5, 1, 22): return - result = self.sg.follow(self.human_user, self.shot) - assert(result['followed']) + with self.gen_entity( + "HumanUser", + projects=[self.project], + ) as human_user, self.gen_entity( + "Shot", + project=self.project, + ) as shot: + result = self.sg.follow(human_user, shot) + assert(result['followed']) + + result = self.sg.followers(shot) - result = self.sg.followers(self.shot) - self.assertEqual(1, len(result)) - self.assertEqual(self.human_user['id'], result[0]['id']) + self.assertEqual(1, len(result)) + self.assertEqual(human_user['id'], result[0]['id']) def test_following(self): '''Test following method''' @@ -1736,42 +1742,53 @@ def test_following(self): warnings.warn("Test bypassed because SG server used does not support this feature.", FutureWarning) return - result = self.sg.follow(self.human_user, self.shot) - assert(result['followed']) - - result = self.sg.following(self.human_user) - self.assertEqual(1, len(result)) - self.assertEqual(self.shot['id'], result[0]['id']) - - result = self.sg.follow(self.human_user, self.task) - assert(result['followed']) - - result = self.sg.following(self.human_user) - self.assertEqual(2, len(result)) - result = self.sg.following(self.human_user, entity_type="Task") - self.assertEqual(1, len(result)) - result = self.sg.following(self.human_user, entity_type="Shot") - self.assertEqual(1, len(result)) - - shot_project_id = self.sg.find_one("Shot", - [["id", "is", self.shot["id"]]], - ["project.Project.id"])["project.Project.id"] - task_project_id = self.sg.find_one("Task", - [["id", "is", self.task["id"]]], - ["project.Project.id"])["project.Project.id"] - project_count = 2 if shot_project_id == task_project_id else 1 - result = self.sg.following(self.human_user, project={"type": "Project", "id": shot_project_id}) - self.assertEqual(project_count, len(result)) - result = self.sg.following(self.human_user, project={"type": "Project", "id": task_project_id}) - self.assertEqual(project_count, len(result)) - result = self.sg.following(self.human_user, - project={"type": "Project", "id": shot_project_id}, - entity_type="Shot") - self.assertEqual(1, len(result)) - result = self.sg.following(self.human_user, - project={"type": "Project", "id": task_project_id}, - entity_type="Task") - self.assertEqual(1, len(result)) + with self.gen_entity( + "HumanUser", + projects=[self.project], + ) as human_user, self.gen_entity( + "Shot", + project=self.project, + ) as shot, self.gen_entity( + "Task", + project=self.project, + ) as task: + result = self.sg.follow(human_user, shot) + assert(result['followed']) + + result = self.sg.following(human_user) + + self.assertEqual(1, len(result)) + + result = self.sg.follow(human_user, task) + assert(result['followed']) + + result = self.sg.following(human_user) + + self.assertEqual(2, len(result)) + result = self.sg.following(human_user, entity_type="Task") + self.assertEqual(1, len(result)) + result = self.sg.following(human_user, entity_type="Shot") + self.assertEqual(1, len(result)) + + shot_project_id = self.sg.find_one("Shot", + [["id", "is", shot["id"]]], + ["project.Project.id"])["project.Project.id"] + task_project_id = self.sg.find_one("Task", + [["id", "is", task["id"]]], + ["project.Project.id"])["project.Project.id"] + project_count = 2 if shot_project_id == task_project_id else 1 + result = self.sg.following(human_user, project={"type": "Project", "id": shot_project_id}) + self.assertEqual(project_count, len(result)) + result = self.sg.following(human_user, project={"type": "Project", "id": task_project_id}) + self.assertEqual(project_count, len(result)) + result = self.sg.following(human_user, + project={"type": "Project", "id": shot_project_id}, + entity_type="Shot") + self.assertEqual(1, len(result)) + result = self.sg.following(human_user, + project={"type": "Project", "id": task_project_id}, + entity_type="Task") + self.assertEqual(1, len(result)) class TestErrors(base.TestBase): @@ -1944,6 +1961,12 @@ class TestScriptUserSudoAuth(base.LiveTestBase): def setUp(self): super(TestScriptUserSudoAuth, self).setUp('ApiUser') + self.sg.update( + 'HumanUser', + self.human_user['id'], + {'projects': [self.project]}, + ) + def test_user_is_creator(self): """ Test 'sudo_as_login' option: on create, ensure appropriate user is set in created-by From 7cb33675e39391d3e20a5c01ac22714c2482dd37 Mon Sep 17 00:00:00 2001 From: Carlos Villavicencio <123113322+carlos-villavicencio-adsk@users.noreply.github.com> Date: Fri, 22 Sep 2023 08:41:58 -0500 Subject: [PATCH 030/125] SG-32806 Enable support for Python 3.10 in CI (#301) * Test CI with Python 3.10 * Add Support for SSLContext * Update badges and docs * Remove 3.11. See note in run-tests.yml * Update badge. Update maxParallel --- README.md | 4 ++-- azure-pipelines-templates/run-tests.yml | 10 +++++++++- shotgun_api3/shotgun.py | 18 +++++++++++++----- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 916fb70df..43632108f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -[![VFX Platform](https://img.shields.io/badge/vfxplatform-2020-blue.svg)](http://www.vfxplatform.com/) -[![Python 3.7](https://img.shields.io/badge/python-3.7-blue.svg)](https://www.python.org/) +[![VFX Platform](https://img.shields.io/badge/vfxplatform-2023%202022%202021%202020-blue.svg)](http://www.vfxplatform.com/) +[![Python 3.7 3.9 3.10](https://img.shields.io/badge/python-3.7%20%7C%203.9%20%7C%203.10-blue.svg)](https://www.python.org/) [![Reference Documentation](http://img.shields.io/badge/doc-reference-blue.svg)](http://developer.shotgridsoftware.com/python-api) [![Build Status](https://dev.azure.com/shotgun-ecosystem/Python%20API/_apis/build/status/shotgunsoftware.python-api?branchName=master)](https://dev.azure.com/shotgun-ecosystem/Python%20API/_build/latest?definitionId=108&branchName=master) [![Coverage Status](https://coveralls.io/repos/github/shotgunsoftware/python-api/badge.svg?branch=master)](https://coveralls.io/github/shotgunsoftware/python-api?branch=master) diff --git a/azure-pipelines-templates/run-tests.yml b/azure-pipelines-templates/run-tests.yml index 31782ef0e..c075746e6 100644 --- a/azure-pipelines-templates/run-tests.yml +++ b/azure-pipelines-templates/run-tests.yml @@ -43,11 +43,19 @@ jobs: # matrix. strategy: matrix: - # We support these two versions of Python. + # We support these versions of Python. Python37: python.version: '3.7' Python39: python.version: '3.9' + Python310: + python.version: '3.10' + # Note for Python 3.11. This will raise hundres of warnings on a third party module + # pytest_nunit/nunit.py + # DeprecationWarning: Use setlocale(), getencoding() and getlocale() instead + # This is the current behavior on the latest 1.0.3 version of this module. + + maxParallel: 3 # These are the steps that will be executed inside each job. steps: diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index 00087a5a7..7d20906e0 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -4244,11 +4244,19 @@ def connect(self): "Connect to a host on a given (SSL) port." http_client.HTTPConnection.connect(self) # Now that the regular HTTP socket has been created, wrap it with our SSL certs. - self.sock = ssl.wrap_socket( - self.sock, - ca_certs=self.__ca_certs, - cert_reqs=ssl.CERT_REQUIRED - ) + if six.PY38: + context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + context.verify_mode = ssl.CERT_REQUIRED + context.check_hostname = False + if self.__ca_certs: + context.load_verify_locations(self.__ca_certs) + self.sock = context.wrap_socket(self.sock) + else: + self.sock = ssl.wrap_socket( + self.sock, + ca_certs=self.__ca_certs, + cert_reqs=ssl.CERT_REQUIRED + ) class CACertsHTTPSHandler(urllib.request.HTTPSHandler): From 11db780c4a18993130a988d73f9e50bd7d17e53f Mon Sep 17 00:00:00 2001 From: Carlos Villavicencio <123113322+carlos-villavicencio-adsk@users.noreply.github.com> Date: Fri, 22 Sep 2023 09:19:55 -0500 Subject: [PATCH 031/125] Packaging for the v3.4.0 (#306) --- HISTORY.rst | 9 +++++++++ setup.py | 4 ++-- shotgun_api3/shotgun.py | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index abbd9d8ab..14e1b9003 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -4,6 +4,15 @@ ShotGrid Python API Changelog Here you can see the full list of changes between each Python API release. +v3.4.0 (2023 Sep 22) +==================== +- Started support for Python 3.10 for CI. +- Add documentation for PublishedFiles preset filters. +- Upgrade httplib2 to 0.22.0. +- Update licensing. +- Updates Autodesk URLs. +- Fix flaky tests. + v3.3.6 (2023 Aug 29) ==================== - Update docs for entity fields. diff --git a/setup.py b/setup.py index 12e5e6209..1df41044a 100644 --- a/setup.py +++ b/setup.py @@ -27,8 +27,8 @@ setup( name='shotgun_api3', - version='3.3.6', - description='Shotgun Python API ', + version='3.4.0', + description='ShotGrid Python API ', long_description=readme, author='ShotGrid Software', author_email='https://www.autodesk.com/support/contact-support', diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index 7d20906e0..e0af1cf44 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -122,7 +122,7 @@ def _is_mimetypes_broken(): # ---------------------------------------------------------------------------- # Version -__version__ = "3.3.6" +__version__ = "3.4.0" # ---------------------------------------------------------------------------- # Errors From 4a8b26f57a351b4440e1b598c153564a4bfd3574 Mon Sep 17 00:00:00 2001 From: Norberto Moreno Date: Wed, 11 Oct 2023 08:28:17 -0500 Subject: [PATCH 032/125] SG-31927 Flaky Test In python-api Test Work schedule (#310) * Remove unused assertion in test_work_schedule test. * Adding some clarifying test comments. * Remove old FIXME comment, it's been fixed by 'SG-24020: Fixes a bug in the schedule test(#250)' --- tests/test_api.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/test_api.py b/tests/test_api.py index fbb74eebc..cf4b153dc 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -785,17 +785,16 @@ def test_work_schedule(self): end_date_obj = datetime.datetime(2012, 1, 7) project = self.project - # We're going to be comparing this value with the value returned from the server, so extract only the type - # and id + # We're going to be comparing this value with the value returned from the server, so extract only the type, id + # and name user = {"type": self.human_user["type"], "id": self.human_user["id"], "name": self.human_user["name"]} work_schedule = self.sg.work_schedule_read(start_date, end_date, project, user) - + # Test that the work_schedule_read api method is called with the 'start_date' and 'end_date' arguments + # in the 'YYYY-MM-DD' string format. self.assertRaises(shotgun_api3.ShotgunError, self.sg.work_schedule_read, start_date_obj, end_date_obj, project, user) - resp = self.sg.work_schedule_read(start_date, end_date, project, user) - self.assertEqual(work_schedule, resp) resp = self.sg.work_schedule_update('2012-01-02', False, 'Studio Holiday') expected = { @@ -843,7 +842,6 @@ def test_work_schedule(self): self.assertEqual(expected, resp) resp = self.sg.work_schedule_read(start_date, end_date, project, user) work_schedule['2012-01-04'] = {"reason": "USER_EXCEPTION", "working": False, "description": "Artist Holiday"} - # FIXME: There seems to be a regresion on the Shotgun server that needs to be fixed. Disabling the test self.assertEqual(work_schedule, resp) # For now disable tests that are erroneously failling on some sites to From 8b50bab3c18c001dbc5f5c52c66a75cb1529c995 Mon Sep 17 00:00:00 2001 From: Julien Langlois <16244608+julien-lang@users.noreply.github.com> Date: Wed, 11 Oct 2023 08:50:08 -0700 Subject: [PATCH 033/125] SG-32773 Fix documentation regarding "in" filter prototype (#309) * Fix documentation regarding "in" filter prototype --- docs/reference.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference.rst b/docs/reference.rst index e6f705ede..94f70b26c 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -289,7 +289,7 @@ Operators and Arguments # note that brackets are not literal (eg. ['start_date', 'in_last', 1, 'DAY']) 'in_next' [[int], 'HOUR' | 'DAY' | 'WEEK' | 'MONTH' | 'YEAR'] # note that brackets are not literal (eg. ['start_date', 'in_next', 1, 'DAY']) - 'in' [[field_value] | None, ...] # Array of field values + 'in' [[field_value], ...] # Array of field values 'type_is' [string] | None # Shotgun entity type 'type_is_not' [string] | None # Shotgun entity type 'in_calendar_day' [int] # Offset (e.g. 0 = today, 1 = tomorrow, From cb031e2fecbb258a8ccf35b477ed2cfc331ffe42 Mon Sep 17 00:00:00 2001 From: Julien Langlois <16244608+julien-lang@users.noreply.github.com> Date: Wed, 11 Oct 2023 10:35:59 -0700 Subject: [PATCH 034/125] Travis badge image is no working anymore. We don't even use Travis anyway (#311) --- docs/index.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index 96ac44db0..177453e46 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -4,7 +4,6 @@ ShotGrid Python API3 Release |version|. (:ref:`Installation `) .. image:: https://img.shields.io/badge/shotgun-api-blue.svg -.. image:: https://secure.travis-ci.org/shotgunsoftware/python-api.svg?branch=master From 66a65ef50ee7adea2922f90abe9a1d3acc63f386 Mon Sep 17 00:00:00 2001 From: Carlos Villavicencio <123113322+carlos-villavicencio-adsk@users.noreply.github.com> Date: Tue, 17 Oct 2023 08:28:32 -0500 Subject: [PATCH 035/125] Add SECURITY.md file (#312) --- SECURITY.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..b8899696a --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,35 @@ +# Security Policy + +## Security + +At Autodesk, we know that the security of your data is critical to your studio’s +operation. +As the industry shifts to the cloud, ShotGrid knows that security and service +models are more important than ever. + +The confidentiality, integrity, and availability of your content is at the top +of our priority list. +Not only do we have a team of ShotGrid engineers dedicated to platform security +and performance, we are also backed by Autodesk’s security team, also invests +heavily in the security for broad range of industries and customers. +We constantly reassess, develop, and improve our risk management program because +we know that the landscape of security is ever-changing. + +If you believe you have found a security vulnerability in any ShotGrid-owned +repository, please report it to us as described below. + + +## Reporting Security Issues + +**Please do not report security vulnerabilities through public GitHub issues.** + +Instead, please report them by sending a message to +[Autodesk Trust Center](https://www.autodesk.com/trust/contact-us). + +Please include as much information as you can provide such as locations, +configurations, reproduction steps, exploit code, impact, etc. + + +## Additional Information + +Please check out the [ShotGrid Security White Paper](https://help.autodesk.com/view/SGSUB/ENU/?guid=SG_Administrator_ar_general_security_ar_security_white_paper_html). \ No newline at end of file From 9ade6cf9d655cf2c41d9962c9cf6627ee26557cf Mon Sep 17 00:00:00 2001 From: Norberto Moreno Date: Thu, 26 Oct 2023 10:29:06 -0500 Subject: [PATCH 036/125] Fix flaky upload tests. (#313) Fix flaky upload tests. --- tests/test_api.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/test_api.py b/tests/test_api.py index cf4b153dc..be3201fd6 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -336,8 +336,8 @@ def test_upload_thumbnail_in_create(self): h = Http(".cache") thumb_resp, content = h.request(new_version.get('image'), "GET") - self.assertEqual(thumb_resp['status'], '200') - self.assertEqual(thumb_resp['content-type'], 'image/jpeg') + self.assertIn(thumb_resp['status'], ['200', '304']) + self.assertIn(thumb_resp['content-type'], ['image/jpeg', 'image/png']) self.sg.delete("Version", new_version['id']) @@ -378,8 +378,8 @@ def test_upload_thumbnail_for_version(self): h = Http(".cache") thumb_resp, content = h.request(version_with_thumbnail.get('image'), "GET") - self.assertEqual(thumb_resp['status'], '200') - self.assertEqual(thumb_resp['content-type'], 'image/jpeg') + self.assertIn(thumb_resp['status'], ['200', '304']) + self.assertIn(thumb_resp['content-type'], ['image/jpeg', 'image/png']) # clear thumbnail response_clear_thumbnail = self.sg.update("Version", self.version['id'], {'image': None}) @@ -405,8 +405,8 @@ def test_upload_thumbnail_for_task(self): h = Http(".cache") thumb_resp, content = h.request(task_with_thumbnail.get('image'), "GET") - self.assertEqual(thumb_resp['status'], '200') - self.assertEqual(thumb_resp['content-type'], 'image/jpeg') + self.assertIn(thumb_resp['status'], ['200', '304']) + self.assertIn(thumb_resp['content-type'], ['image/jpeg', 'image/png']) # clear thumbnail response_clear_thumbnail = self.sg.update("Version", self.version['id'], {'image': None}) @@ -510,8 +510,8 @@ def test_linked_thumbnail_url(self): h = Http(".cache") thumb_resp, content = h.request(response_version_with_project.get('project.Project.image'), "GET") - self.assertEqual(thumb_resp['status'], '200') - self.assertEqual(thumb_resp['content-type'], 'image/jpeg') + self.assertIn(thumb_resp['status'], ['200', '304']) + self.assertIn(thumb_resp['content-type'], ['image/jpeg', 'image/png']) else: expected_version_with_project = { @@ -2067,8 +2067,8 @@ def test_humanuser_upload_thumbnail_for_version(self): h = Http(".cache") thumb_resp, content = h.request(version_with_thumbnail.get('image'), "GET") - self.assertEqual(thumb_resp['status'], '200') - self.assertEqual(thumb_resp['content-type'], 'image/jpeg') + self.assertIn(thumb_resp['status'], ['200', '304']) + self.assertIn(thumb_resp['content-type'], ['image/jpeg', 'image/png']) # clear thumbnail response_clear_thumbnail = self.sg.update("Version", self.version['id'], {'image': None}) @@ -2124,8 +2124,8 @@ def test_humanuser_upload_thumbnail_for_version(self): h = Http(".cache") thumb_resp, content = h.request(version_with_thumbnail.get('image'), "GET") - self.assertEqual(thumb_resp['status'], '200') - self.assertEqual(thumb_resp['content-type'], 'image/jpeg') + self.assertIn(thumb_resp['status'], ['200', '304']) + self.assertIn(thumb_resp['content-type'], ['image/jpeg', 'image/png']) # clear thumbnail response_clear_thumbnail = self.sg.update("Version", self.version['id'], {'image': None}) From 6782d01a146a9e845a76a15a2a63c4ad2db9e7c5 Mon Sep 17 00:00:00 2001 From: Julien Langlois <16244608+julien-lang@users.noreply.github.com> Date: Mon, 6 Nov 2023 16:18:07 -0800 Subject: [PATCH 037/125] SG-32817 Update Python Certifi license block (#314) --- software_credits | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/software_credits b/software_credits index 0c70a930a..6cd90620b 100644 --- a/software_credits +++ b/software_credits @@ -47,12 +47,9 @@ The ShotGrid Python API uses the following software. Thanks to their creators, l ============================== Certifi ============================== -This Autodesk software contains the unmodified python-certifi package. -The use and distribution terms for this software are covered by the Mozilla -Public License 2.0 (https://www.mozilla.org/en-US/MPL/2.0/). -By using this software in any fashion, you are agreeing to be bound by the terms -of this license. -The source code for python-certifi is available from https://github.com/certifi/python-certifi. +This Autodesk software contains the python-certifi package and is subject to the +terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not +distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. ============================== Httplib2 ============================== From 072633cdefecf828907b626348da6ac20face841 Mon Sep 17 00:00:00 2001 From: Julien Langlois <16244608+julien-lang@users.noreply.github.com> Date: Tue, 12 Dec 2023 14:42:47 -0800 Subject: [PATCH 038/125] SG-33870 Add methods for the user_subscriptions API end points (#315) * Add methods for the user_subscriptions_read and user_subscriptions_create API end points * Apply suggestions from code review Co-authored-by: Shayna Duguid --------- Co-authored-by: Shayna Duguid --- shotgun_api3/shotgun.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index e0af1cf44..50439f2a4 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -3261,6 +3261,42 @@ def preferences_read(self, prefs=None): return self._call_rpc("preferences_read", {"prefs": prefs}) + def user_subscriptions_read(self): + """ + Get the list of user subscriptions. + + :returns: A list of user subscriptions where each subscription is a + dictionary containing the ``humanUserId`` and ``subscription`` + fields. + :rtype: list + """ + + return self._call_rpc("user_subscriptions_read", None) + + def user_subscriptions_create(self, users: list): + """ + Assign subscriptions to users. + + :param list users: list of user subscriptions to assign. + Each subscription must be a dictionary with the ``humanUserId`` and + ``subscription`` fields. + The ``subscription`` is either ``None``, a single string, or an + array of strings with subscription information. + + :returns: ``True`` if the request succedeed, ``False`` if otherwise. + :rtype: bool + """ + + response = self._call_rpc( + "user_subscriptions_create", + {"users": users} + ) + + if not isinstance(response, dict): + return False + + return response.get("status") == "success" + def _build_opener(self, handler): """ Build urllib2 opener with appropriate proxy handler. From 28b5efec0a1494cf2389530c40ee4c40b94eefdc Mon Sep 17 00:00:00 2001 From: Carlos Villavicencio <123113322+carlos-villavicencio-adsk@users.noreply.github.com> Date: Tue, 19 Dec 2023 13:47:42 -0500 Subject: [PATCH 039/125] SG-33891 Retries also on 504 (#316) * Retries also on 504 * Use assertEqual instead --- shotgun_api3/shotgun.py | 4 ++-- tests/test_client.py | 21 +++++++++++++++++++-- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index 50439f2a4..9626056d2 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -3443,8 +3443,8 @@ def _call_rpc(self, method, params, include_auth_params=True, first=False): # We've seen some rare instances of SG returning 502 for issues that # appear to be caused by something internal to SG. We're going to # allow for limited retries for those specifically. - if attempt != max_attempts and e.errcode == 502: - LOG.debug("Got a 502 response. Waiting and retrying...") + if attempt != max_attempts and e.errcode in [502, 504]: + LOG.debug("Got a 502 or 504 response. Waiting and retrying...") time.sleep(float(attempt) * backoff) attempt += 1 continue diff --git a/tests/test_client.py b/tests/test_client.py index b1ccdbc33..95ceb4dfc 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -430,12 +430,29 @@ def test_call_rpc(self): expected = "rpc response with list result" self.assertEqual(d["results"], rv, expected) - # Test that we raise on a 502. This is ensuring the retries behavior - # in place specific to 502 responses still eventually ends up raising. + # Test that we raise on a 5xx. This is ensuring the retries behavior + # in place specific to 5xx responses still eventually ends up raising. + # 502 d = {"results": ["foo", "bar"]} a = {"some": "args"} self._mock_http(d, status=(502, "bad gateway")) self.assertRaises(api.ProtocolError, self.sg._call_rpc, "list", a) + self.assertEqual( + 4, + self.sg._http_request.call_count, + "Call is repeated up to 3 times", + ) + + # 504 + d = {"results": ["foo", "bar"]} + a = {"some": "args"} + self._mock_http(d, status=(504, "gateway timeout")) + self.assertRaises(api.ProtocolError, self.sg._call_rpc, "list", a) + self.assertEqual( + 4, + self.sg._http_request.call_count, + "Call is repeated up to 3 times", + ) def test_upload_s3(self): """ From da4184c92860ea5d5accf037743dd1ac6488de93 Mon Sep 17 00:00:00 2001 From: Julien Langlois <16244608+julien-lang@users.noreply.github.com> Date: Thu, 11 Jan 2024 10:02:00 -0800 Subject: [PATCH 040/125] SG-34199 Comment typing annotation breaks Python 2 compatibility (#320) * #comment typing annotation breaks Python 2 compatibility We pull `shotgun_api3` directly for deployment here at TTF. We have some old shows still using Maya2019 (and thereby Python 2). This addition of hinting annotations breaks import of your packages. If it's intentional, and you are dropping Py2 support, then disregard this PR. In that case, we'll have to lock our release for our Py2 deployments (while we have them). * Switchng variable syntax from macro to template to get an empty string when the variable does not exist instead of the macro unchanged https://learn.microsoft.com/en-us/azure/devops/pipelines/process/variables?view=azure-devops&tabs=yaml%2Cbatch#macro-syntax-variables * Remove example server_url value from config because is used by CI and sometimes needs an empty value --------- Co-authored-by: Benjamin Slack <51135149+slackba@users.noreply.github.com> --- azure-pipelines-templates/run-tests.yml | 6 +++--- shotgun_api3/shotgun.py | 3 ++- tests/example_config | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/azure-pipelines-templates/run-tests.yml b/azure-pipelines-templates/run-tests.yml index c075746e6..dc2734750 100644 --- a/azure-pipelines-templates/run-tests.yml +++ b/azure-pipelines-templates/run-tests.yml @@ -101,9 +101,9 @@ jobs: # Pass the values needed to authenticate with the Shotgun site and create some entities. # Remember, on a pull request from a client or on forked repos, those variables # will be empty! - SG_SERVER_URL: $(ci_site) - SG_SCRIPT_NAME: $(ci_site_script_name) - SG_API_KEY: $(ci_site_script_key) + SG_SERVER_URL: ${{ variables.ci_site }} + SG_SCRIPT_NAME: ${{ variables.ci_site_script_name }} + SG_API_KEY: ${{ variables.ci_site_script_key }} # The unit tests manipulate the user and project during the tests, which can cause collisions, # so sandbox each build variant. # Ideally, we would use the agent name here. The problem is that the agent name is in a build diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index 9626056d2..200717088 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -3273,7 +3273,8 @@ def user_subscriptions_read(self): return self._call_rpc("user_subscriptions_read", None) - def user_subscriptions_create(self, users: list): + def user_subscriptions_create(self, users): + # type: (list[dict[str, Union[str, list[str], None]) -> bool """ Assign subscriptions to users. diff --git a/tests/example_config b/tests/example_config index 01501d23d..f48fedcdc 100644 --- a/tests/example_config +++ b/tests/example_config @@ -16,7 +16,7 @@ # Full url to the ShotGrid server server # e.g. https://my-site.shotgrid.autodesk.com # be careful to not end server_url with a "/", or some tests may fail -server_url : http://0.0.0.0:3000 +server_url : # script name as key as listed in admin panel for your server script_name : test script name api_key : test script api key From a9755313083d381cf9d24de6b0fdcfbb58e32a23 Mon Sep 17 00:00:00 2001 From: Xin Liu Date: Mon, 22 Jan 2024 14:00:11 -0500 Subject: [PATCH 041/125] Add user_subscription_read and user_subscription_create methods to docs (#322) --- docs/reference.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/reference.rst b/docs/reference.rst index 94f70b26c..e7558f65c 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -123,6 +123,14 @@ control, these methods are available. .. automethod:: Shotgun.set_session_uuid .. automethod:: Shotgun.info +Subscription Management +======================= + +These methods are used for reading and assigning user subscriptions. + +.. automethod:: Shotgun.user_subscriptions_read +.. automethod:: Shotgun.user_subscriptions_create + CRUD Methods ============ From 94edc8d940626089a40650f99387ef6ada8b85e2 Mon Sep 17 00:00:00 2001 From: Carlos Villavicencio <123113322+carlos-villavicencio-adsk@users.noreply.github.com> Date: Wed, 24 Jan 2024 14:56:51 -0500 Subject: [PATCH 042/125] SG-34359 Add field type entity_type to mockgun as simple str (#323) * add entity_type as simple str * Dummy change to trigger CI --------- Co-authored-by: slingshotvfx <146885925+slingshotvfx@users.noreply.github.com> --- shotgun_api3/lib/mockgun/mockgun.py | 1 + 1 file changed, 1 insertion(+) diff --git a/shotgun_api3/lib/mockgun/mockgun.py b/shotgun_api3/lib/mockgun/mockgun.py index 6a2f79de0..eab05f88d 100644 --- a/shotgun_api3/lib/mockgun/mockgun.py +++ b/shotgun_api3/lib/mockgun/mockgun.py @@ -496,6 +496,7 @@ def _validate_entity_data(self, entity_type, data): "percent": int, "text": six.string_types, "serializable": dict, + "entity_type": six.string_types, "date": datetime.date, "date_time": datetime.datetime, "list": six.string_types, From 37af3157a2ee15759dfabbbc10c2cce32d3c9818 Mon Sep 17 00:00:00 2001 From: Carlos Villavicencio <123113322+carlos-villavicencio-adsk@users.noreply.github.com> Date: Fri, 26 Jan 2024 13:09:07 -0500 Subject: [PATCH 043/125] Use Azure Pipelines group variables (#325) * Put server_url back in example_config * Add variable group name * Update syntax * Update syntax * Change yml block --- azure-pipelines-templates/run-tests.yml | 9 ++++++--- tests/example_config | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/azure-pipelines-templates/run-tests.yml b/azure-pipelines-templates/run-tests.yml index dc2734750..7c7edf8e9 100644 --- a/azure-pipelines-templates/run-tests.yml +++ b/azure-pipelines-templates/run-tests.yml @@ -57,6 +57,9 @@ jobs: maxParallel: 3 + variables: + group: sg-credentials + # These are the steps that will be executed inside each job. steps: # Specifies which version of Python we want to use. That's where the strategy comes in. @@ -101,9 +104,9 @@ jobs: # Pass the values needed to authenticate with the Shotgun site and create some entities. # Remember, on a pull request from a client or on forked repos, those variables # will be empty! - SG_SERVER_URL: ${{ variables.ci_site }} - SG_SCRIPT_NAME: ${{ variables.ci_site_script_name }} - SG_API_KEY: ${{ variables.ci_site_script_key }} + SG_SERVER_URL: $(ci_site) + SG_SCRIPT_NAME: $(ci_site_script_name) + SG_API_KEY: $(ci_site_script_key) # The unit tests manipulate the user and project during the tests, which can cause collisions, # so sandbox each build variant. # Ideally, we would use the agent name here. The problem is that the agent name is in a build diff --git a/tests/example_config b/tests/example_config index f48fedcdc..01501d23d 100644 --- a/tests/example_config +++ b/tests/example_config @@ -16,7 +16,7 @@ # Full url to the ShotGrid server server # e.g. https://my-site.shotgrid.autodesk.com # be careful to not end server_url with a "/", or some tests may fail -server_url : +server_url : http://0.0.0.0:3000 # script name as key as listed in admin panel for your server script_name : test script name api_key : test script api key From d504a5aa63ec3ca688466e14951257a0eb89cdd4 Mon Sep 17 00:00:00 2001 From: Carlos Villavicencio <123113322+carlos-villavicencio-adsk@users.noreply.github.com> Date: Mon, 29 Jan 2024 08:11:05 -0500 Subject: [PATCH 044/125] SG-34197 Retry S3 uploads on error 500 (#324) * Retry S3 uploads on error 500 * Remove context manager variable * Improve conditional logic --- shotgun_api3/shotgun.py | 10 ++++------ tests/base.py | 4 ++-- tests/test_client.py | 26 +++++++++++++++++++++++++- 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index 200717088..a3b789748 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -4145,16 +4145,14 @@ def _upload_data_to_storage(self, data, content_type, size, storage_url): LOG.debug("Completed request to %s" % request.get_method()) except urllib.error.HTTPError as e: - if e.code == 500: - raise ShotgunError("Server encountered an internal error.\n%s\n%s\n\n" % (storage_url, e)) - elif attempt != max_attempts and e.code == 503: - LOG.debug("Got a 503 response. Waiting and retrying...") + if attempt != max_attempts and e.code in [500, 503]: + LOG.debug("Got a %s response. Waiting and retrying..." % e.code) time.sleep(float(attempt) * backoff) attempt += 1 continue + elif e.code in [500, 503]: + raise ShotgunError("Got a %s response when uploading to %s: %s" % (e.code, storage_url, e)) else: - if e.code == 503: - raise ShotgunError("Got a 503 response when uploading to %s: %s" % (storage_url, e)) raise ShotgunError("Unanticipated error occurred uploading to %s: %s" % (storage_url, e)) else: diff --git a/tests/base.py b/tests/base.py index 8296061a1..5f4b61aa0 100644 --- a/tests/base.py +++ b/tests/base.py @@ -119,7 +119,7 @@ def setUp(self): self._setup_mock() self._setup_mock_data() - def _setup_mock(self): + def _setup_mock(self, s3_status_code_error=503): """Setup mocking on the ShotgunClient to stop it calling a live server """ # Replace the function used to make the final call to the server @@ -131,7 +131,7 @@ def _setup_mock(self): self.sg._make_upload_request = mock.Mock(spec=api.Shotgun._make_upload_request, side_effect = urllib.error.HTTPError( "url", - 503, + s3_status_code_error, "The server is currently down or to busy to reply." "Please try again later.", {}, diff --git a/tests/test_client.py b/tests/test_client.py index 95ceb4dfc..f7e783b2c 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -454,7 +454,7 @@ def test_call_rpc(self): "Call is repeated up to 3 times", ) - def test_upload_s3(self): + def test_upload_s3_503(self): """ Test 503 response is retried when uploading to S3. """ @@ -476,6 +476,30 @@ def test_upload_s3(self): self.assertTrue( max_attempts == self.sg._make_upload_request.call_count, "Call is repeated up to 3 times") + + def test_upload_s3_500(self): + """ + Test 500 response is retried when uploading to S3. + """ + self._setup_mock(s3_status_code_error=500) + this_dir, _ = os.path.split(__file__) + storage_url = "http://foo.com/" + path = os.path.abspath(os.path.expanduser( + os.path.join(this_dir, "sg_logo.jpg"))) + max_attempts = 4 # Max retries to S3 server attempts + # Expected HTTPError exception error message + expected = "The server is currently down or to busy to reply." \ + "Please try again later." + + # Test the Internal function that is used to upload each + # data part in the context of multi-part uploads to S3, we + # simulate the HTTPError exception raised with 503 status errors + with self.assertRaises(api.ShotgunError, msg=expected): + self.sg._upload_file_to_storage(path, storage_url) + # Test the max retries attempt + self.assertTrue( + max_attempts == self.sg._make_upload_request.call_count, + "Call is repeated up to 3 times") def test_transform_data(self): """Outbound data is transformed""" From e3985641c8c32d45de15cd37e3f3724d749257d3 Mon Sep 17 00:00:00 2001 From: Carlos Villavicencio <123113322+carlos-villavicencio-adsk@users.noreply.github.com> Date: Mon, 29 Jan 2024 11:34:43 -0500 Subject: [PATCH 045/125] Packaging for v3.4.1 (#326) --- HISTORY.rst | 14 ++++++++++++++ setup.py | 2 +- shotgun_api3/shotgun.py | 2 +- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 14e1b9003..0fd712230 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -4,6 +4,20 @@ ShotGrid Python API Changelog Here you can see the full list of changes between each Python API release. +v3.4.1 (2023 Jan 29) +==================== +- Flaky Tests +- Documentation: Fix issue regarding "in" filter prototype +- Documentation: Travis badge image is no working anymore +- Documentation: Add ``user_subscription_read`` and ``user_subscription_create`` methods +- Update Python Certifi license block +- Add methods for the user_subscriptions API end points +- Retry ShotGrid request also on error 504 +- Retry S3 uploads on error 500 +- Comment typing annotation breaks Python 2 compatibility +- Add field type ``entity_type`` to mockgun + + v3.4.0 (2023 Sep 22) ==================== - Started support for Python 3.10 for CI. diff --git a/setup.py b/setup.py index 1df41044a..00e140216 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ setup( name='shotgun_api3', - version='3.4.0', + version='3.4.1', description='ShotGrid Python API ', long_description=readme, author='ShotGrid Software', diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index a3b789748..e0ca395b9 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -122,7 +122,7 @@ def _is_mimetypes_broken(): # ---------------------------------------------------------------------------- # Version -__version__ = "3.4.0" +__version__ = "3.4.1" # ---------------------------------------------------------------------------- # Errors From 1666c39956207a9cde377b319c9df45bd9745eb2 Mon Sep 17 00:00:00 2001 From: Carlos Villavicencio <123113322+carlos-villavicencio-adsk@users.noreply.github.com> Date: Mon, 5 Feb 2024 16:10:06 -0500 Subject: [PATCH 046/125] SG-34462 Test CI with Python 3.11 (#327) * Test CI with Python 3.10 * Fix typo * Remove sre_constants deprecation --- azure-pipelines-templates/run-tests.yml | 8 +++----- shotgun_api3/lib/pyparsing.py | 5 ++--- software_credits | 2 ++ tests/ci_requirements.txt | 6 +++++- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/azure-pipelines-templates/run-tests.yml b/azure-pipelines-templates/run-tests.yml index 7c7edf8e9..b73ca1eb0 100644 --- a/azure-pipelines-templates/run-tests.yml +++ b/azure-pipelines-templates/run-tests.yml @@ -50,12 +50,10 @@ jobs: python.version: '3.9' Python310: python.version: '3.10' - # Note for Python 3.11. This will raise hundres of warnings on a third party module - # pytest_nunit/nunit.py - # DeprecationWarning: Use setlocale(), getencoding() and getlocale() instead - # This is the current behavior on the latest 1.0.3 version of this module. + Python311: + python.version: '3.11' - maxParallel: 3 + maxParallel: 4 variables: group: sg-credentials diff --git a/shotgun_api3/lib/pyparsing.py b/shotgun_api3/lib/pyparsing.py index 581d5bbb8..774222bba 100644 --- a/shotgun_api3/lib/pyparsing.py +++ b/shotgun_api3/lib/pyparsing.py @@ -105,7 +105,6 @@ import sys import warnings import re -import sre_constants import collections import pprint import traceback @@ -3310,7 +3309,7 @@ def __init__(self, pattern, flags=0, asGroupList=False, asMatch=False): try: self.re = re.compile(self.pattern, self.flags) self.reString = self.pattern - except sre_constants.error: + except re.error: warnings.warn("invalid pattern (%s) passed to Regex" % pattern, SyntaxWarning, stacklevel=2) raise @@ -3501,7 +3500,7 @@ def __init__(self, quoteChar, escChar=None, escQuote=None, multiline=False, self.re = re.compile(self.pattern, self.flags) self.reString = self.pattern self.re_match = self.re.match - except sre_constants.error: + except re.error: warnings.warn("invalid pattern (%s) passed to Regex" % self.pattern, SyntaxWarning, stacklevel=2) raise diff --git a/software_credits b/software_credits index 6cd90620b..aa6e87c5c 100644 --- a/software_credits +++ b/software_credits @@ -80,6 +80,8 @@ SOFTWARE. ============================== pyparsing ============================== +Copyright (c) 2003-2019 Paul T. McGuire + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including diff --git a/tests/ci_requirements.txt b/tests/ci_requirements.txt index f05c03266..b78d94936 100644 --- a/tests/ci_requirements.txt +++ b/tests/ci_requirements.txt @@ -18,4 +18,8 @@ flake8 pytest pytest-azurepipelines coverage -pytest-coverage \ No newline at end of file +pytest-coverage +# Note for Python 3.11: This will raise a lot of warnings on this third party module +# `DeprecationWarning: Use setlocale(), getencoding() and getlocale()` +# Using this PR until it gets merged +pytest-nunit @ git+https://github.com/pytest-dev/pytest-nunit.git@refs/pull/73/merge From ecf804b3ab53ec94c2068f86278dc79d0dbcb14f Mon Sep 17 00:00:00 2001 From: Carlos Villavicencio <123113322+carlos-villavicencio-adsk@users.noreply.github.com> Date: Tue, 6 Feb 2024 10:22:45 -0500 Subject: [PATCH 047/125] Packaging for v3.4.2 (#329) * Packaging for v3.4.2 * Update vfx badge --- HISTORY.rst | 6 +++++- README.md | 4 ++-- setup.py | 2 +- shotgun_api3/shotgun.py | 2 +- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 0fd712230..0b923f21a 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -4,7 +4,11 @@ ShotGrid Python API Changelog Here you can see the full list of changes between each Python API release. -v3.4.1 (2023 Jan 29) +v3.4.2 (2024 Feb 6) +=================== +- Add support for Python 3.11 + +v3.4.1 (2024 Jan 29) ==================== - Flaky Tests - Documentation: Fix issue regarding "in" filter prototype diff --git a/README.md b/README.md index 43632108f..161612f5b 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -[![VFX Platform](https://img.shields.io/badge/vfxplatform-2023%202022%202021%202020-blue.svg)](http://www.vfxplatform.com/) -[![Python 3.7 3.9 3.10](https://img.shields.io/badge/python-3.7%20%7C%203.9%20%7C%203.10-blue.svg)](https://www.python.org/) +[![VFX Platform](https://img.shields.io/badge/vfxplatform-2024%20%7C%202023%20%7C%202022%20%7C%202021-blue.svg)](http://www.vfxplatform.com/) +[![Python](https://img.shields.io/badge/python-3.7%20%7C%203.9%20%7C%203.10%20%7C%203.11-blue.svg)](https://www.python.org/) [![Reference Documentation](http://img.shields.io/badge/doc-reference-blue.svg)](http://developer.shotgridsoftware.com/python-api) [![Build Status](https://dev.azure.com/shotgun-ecosystem/Python%20API/_apis/build/status/shotgunsoftware.python-api?branchName=master)](https://dev.azure.com/shotgun-ecosystem/Python%20API/_build/latest?definitionId=108&branchName=master) [![Coverage Status](https://coveralls.io/repos/github/shotgunsoftware/python-api/badge.svg?branch=master)](https://coveralls.io/github/shotgunsoftware/python-api?branch=master) diff --git a/setup.py b/setup.py index 00e140216..93a9de948 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ setup( name='shotgun_api3', - version='3.4.1', + version='3.4.2', description='ShotGrid Python API ', long_description=readme, author='ShotGrid Software', diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index e0ca395b9..2ec07823a 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -122,7 +122,7 @@ def _is_mimetypes_broken(): # ---------------------------------------------------------------------------- # Version -__version__ = "3.4.1" +__version__ = "3.4.2" # ---------------------------------------------------------------------------- # Errors From 84c991d6e1b288214df8cf8a9c29046643c8656c Mon Sep 17 00:00:00 2001 From: Carlos Villavicencio <123113322+carlos-villavicencio-adsk@users.noreply.github.com> Date: Wed, 14 Feb 2024 12:07:08 -0500 Subject: [PATCH 048/125] Remove pytest-nunit fix for CI (#331) --- tests/ci_requirements.txt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/ci_requirements.txt b/tests/ci_requirements.txt index b78d94936..92189202a 100644 --- a/tests/ci_requirements.txt +++ b/tests/ci_requirements.txt @@ -19,7 +19,3 @@ pytest pytest-azurepipelines coverage pytest-coverage -# Note for Python 3.11: This will raise a lot of warnings on this third party module -# `DeprecationWarning: Use setlocale(), getencoding() and getlocale()` -# Using this PR until it gets merged -pytest-nunit @ git+https://github.com/pytest-dev/pytest-nunit.git@refs/pull/73/merge From 94718c4b23ff1a9296fa57a4c78ce422ef5692ff Mon Sep 17 00:00:00 2001 From: juanburgosautoglb <156214539+juanburgosautoglb@users.noreply.github.com> Date: Wed, 20 Mar 2024 11:34:36 -0500 Subject: [PATCH 049/125] SG-34087 - Rebranding (#321) * Change all the shotgrid/shotgun occurrences in the repository. * fix PRs comments * fix PRs comments * change SG to PTR * fix PR comments * fix PR comments * Fix PR comments. * Update images. --- HISTORY.rst | 8 +-- README.md | 8 +-- SECURITY.md | 8 +-- azure-pipelines-templates/run-tests.yml | 2 +- docs/advanced/packaging.rst | 6 +- docs/authentication.rst | 22 +++---- docs/cookbook.rst | 8 +-- docs/cookbook/attachments.rst | 32 +++++------ .../examples/ami_version_packager.rst | 12 ++-- docs/cookbook/examples/basic_create_shot.rst | 10 ++-- .../basic_create_shot_task_template.rst | 6 +- .../basic_create_version_link_shot.rst | 4 +- docs/cookbook/examples/basic_delete_shot.rst | 4 +- docs/cookbook/examples/basic_find_shot.rst | 6 +- docs/cookbook/examples/basic_sg_instance.rst | 12 ++-- docs/cookbook/examples/basic_update_shot.rst | 10 ++-- .../basic_upload_thumbnail_version.rst | 8 +-- docs/cookbook/examples/svn_integration.rst | 40 ++++++------- docs/cookbook/smart_cut_fields.rst | 2 +- docs/cookbook/tasks/split_tasks.rst | 2 +- docs/cookbook/tasks/task_dependencies.rst | 8 +-- docs/cookbook/tasks/updating_tasks.rst | 14 ++--- docs/cookbook/tutorials.rst | 2 +- docs/cookbook/usage_tips.rst | 18 +++--- docs/images/scripts_page.png | Bin 73074 -> 72478 bytes docs/index.rst | 14 ++--- docs/installation.rst | 4 +- docs/reference.rst | 54 +++++++++--------- setup.py | 4 +- shotgun_api3/lib/README.md | 14 ++--- shotgun_api3/lib/mockgun/mockgun.py | 4 +- shotgun_api3/shotgun.py | 18 +++--- software_credits | 2 +- tests/base.py | 4 +- tests/example_config | 4 +- tests/mockgun/schema.pickle | 2 +- tests/test_api.py | 18 +++--- tests/test_client.py | 4 +- tests/test_unit.py | 4 +- 39 files changed, 201 insertions(+), 201 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 0b923f21a..6e3f139fb 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,6 +1,6 @@ -***************************** -ShotGrid Python API Changelog -***************************** +********************************************* +Flow Production Tracking Python API Changelog +********************************************* Here you can see the full list of changes between each Python API release. @@ -173,7 +173,7 @@ v3.0.32 (2016 Sep 22) - Optimized import speed of the API on Python 2.7. - Integrated the latest fixes to the ``mimetypes`` module. -- Added ``nav_expand()`` method as an experimental, internal method for querying SG hierarchy. +- Added ``nav_expand()`` method as an experimental, internal method for querying SG hierarchy. - Ported all documentation to sphinx. See http://developer.shotgridsoftware.com/python-api. - Moved Changelog to dedicated HISTORY file. diff --git a/README.md b/README.md index 161612f5b..eaf1e9ccc 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ [![Build Status](https://dev.azure.com/shotgun-ecosystem/Python%20API/_apis/build/status/shotgunsoftware.python-api?branchName=master)](https://dev.azure.com/shotgun-ecosystem/Python%20API/_build/latest?definitionId=108&branchName=master) [![Coverage Status](https://coveralls.io/repos/github/shotgunsoftware/python-api/badge.svg?branch=master)](https://coveralls.io/github/shotgunsoftware/python-api?branch=master) -# ShotGrid Python API +# Flow Production Tracking Python API -ShotGrid provides a simple Python-based API for accessing ShotGrid and integrating with other tools. This is the official API that is maintained by ShotGrid Software (https://knowledge.autodesk.com/contact-support) +Autodesk provides a simple Python-based API for accessing Flow Production Tracking and integrating with other tools. This is the official API that is maintained by Autodesk (https://knowledge.autodesk.com/contact-support) The latest version can always be found at http://github.com/shotgunsoftware/python-api @@ -48,8 +48,8 @@ Integration and unit tests are provided. - A `tests/config` file (you can copy an example from `tests/example_config`). - Tests can be run individually like this: `nosetests --config="nose.cfg" tests/test_client.py` - Make sure to not forget the `--config="nose.cfg"` option. This option tells nose to use our config file. This will exclude python 2- and 3-specific files in the `/lib` directory, preventing a failure from being reported by nose for compilation due to incompatible syntax in those files. -- `test_client` and `tests_unit` use mock server interaction and do not require a ShotGrid instance to be available (no modifications to `tests/config` are necessary). -- `test_api` and `test_api_long` *do* require a ShotGrid instance, with a script key available for the tests. The server and script user values must be supplied in the `tests/config` file. The tests will add test data to your server based on information in your config. This data will be manipulated by the tests, and should not be used for other purposes. +- `test_client` and `tests_unit` use mock server interaction and do not require a Flow Production Tracking instance to be available (no modifications to `tests/config` are necessary). +- `test_api` and `test_api_long` *do* require a Flow Production Tracking instance, with a script key available for the tests. The server and script user values must be supplied in the `tests/config` file. The tests will add test data to your server based on information in your config. This data will be manipulated by the tests, and should not be used for other purposes. - To run all of the tests, use the shell script `run-tests`. ## Release process diff --git a/SECURITY.md b/SECURITY.md index b8899696a..c32c73245 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -4,18 +4,18 @@ At Autodesk, we know that the security of your data is critical to your studio’s operation. -As the industry shifts to the cloud, ShotGrid knows that security and service +As the industry shifts to the cloud, Flow Production Tracking knows that security and service models are more important than ever. The confidentiality, integrity, and availability of your content is at the top of our priority list. -Not only do we have a team of ShotGrid engineers dedicated to platform security +Not only do we have a team of Flow Production Tracking engineers dedicated to platform security and performance, we are also backed by Autodesk’s security team, also invests heavily in the security for broad range of industries and customers. We constantly reassess, develop, and improve our risk management program because we know that the landscape of security is ever-changing. -If you believe you have found a security vulnerability in any ShotGrid-owned +If you believe you have found a security vulnerability in any Flow Production Tracking-owned repository, please report it to us as described below. @@ -32,4 +32,4 @@ configurations, reproduction steps, exploit code, impact, etc. ## Additional Information -Please check out the [ShotGrid Security White Paper](https://help.autodesk.com/view/SGSUB/ENU/?guid=SG_Administrator_ar_general_security_ar_security_white_paper_html). \ No newline at end of file +Please check out the [Flow Production Tracking Security White Paper](https://help.autodesk.com/view/SGSUB/ENU/?guid=SG_Administrator_ar_general_security_ar_security_white_paper_html). \ No newline at end of file diff --git a/azure-pipelines-templates/run-tests.yml b/azure-pipelines-templates/run-tests.yml index b73ca1eb0..34345a332 100644 --- a/azure-pipelines-templates/run-tests.yml +++ b/azure-pipelines-templates/run-tests.yml @@ -99,7 +99,7 @@ jobs: pytest -v --cov shotgun_api3 --cov-report xml --test-run-title="${{parameters.name}}-$(python.version)" displayName: Running tests env: - # Pass the values needed to authenticate with the Shotgun site and create some entities. + # Pass the values needed to authenticate with the Flow Production Tracking site and create some entities. # Remember, on a pull request from a client or on forked repos, those variables # will be empty! SG_SERVER_URL: $(ci_site) diff --git a/docs/advanced/packaging.rst b/docs/advanced/packaging.rst index 630a32f44..d46426e73 100644 --- a/docs/advanced/packaging.rst +++ b/docs/advanced/packaging.rst @@ -14,8 +14,8 @@ There are caveats you need to be aware of when creating such an app. ******************************** HTTPS Validation and cacerts.txt ******************************** -When creating the connection to ShotGrid a file is used to validate the ShotGrid certificate. This -file is located at ``shotgun_api3/lib/httplib2/cacerts.txt``. Because this file is not a Python +When creating the connection to Flow Production Tracking, a file is used to validate the Flow Production Tracking +certificate. This file is located at ``shotgun_api3/lib/httplib2/cacerts.txt``. Because this file is not a Python file imported by your application, py2app will not know to include it in your package, it will need to be explicitly specified in your ``setup.py`` file (edit the path based on the location where your ``shotgun_api3`` package is located):: @@ -31,7 +31,7 @@ following structure:: ./Contents/Resources/my_script.py Where in ``my_script.py`` you can access the ``cacerts.txt`` file using a relative path to pass it -into the ShotGrid connection's constructor:: +into the Flow Production Tracking connection's constructor:: ca_certs = os.path.join(os.path.dirname(__file__), 'shotgun_api3', 'cacerts.txt') sg = shotgun_api3.Shotgun('https://my-site.shotgrid.autodesk.com', 'script_name', 'script_key', diff --git a/docs/authentication.rst b/docs/authentication.rst index 445820248..0e5fe8572 100644 --- a/docs/authentication.rst +++ b/docs/authentication.rst @@ -2,7 +2,7 @@ Authentication ############## -In order to communicate with your ShotGrid server via the API, you must provide valid authentication credentials. The API allows you to authenticate with user-based, or script-based credentials. +In order to communicate with your server via the API, you must provide valid authentication credentials. The API allows you to authenticate with user-based, or script-based credentials. ************************* User-based Authentication @@ -17,24 +17,24 @@ When authenticating as a user, you provide your normal login and password when i *************************** Script-based Authentication *************************** -In order to authenticate as a script, your script must be :ref:`registered with ShotGrid and have a valid API key `. When creating your :class:`shotgun_api3.Shotgun` object, provide the ``script_name`` and ``api_key``.:: +In order to authenticate as a script, your script must be :ref:`registered with Flow Production Tracking and have a valid API key `. When creating your :class:`shotgun_api3.Shotgun` object, provide the ``script_name`` and ``api_key``.:: sg = shotgun_api3.Shotgun("https://my-site.shotgrid.autodesk.com", script_name="compress", api_key="0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef") -.. note:: When using script-based authentication, we **strongly** recommend you register each script separately with ShotGrid and have individual API keys for each. This allows you to track down each of your scripts and the actions they are performing much more accurately in the event logs. +.. note:: When using script-based authentication, we **strongly** recommend you register each script separately with Flow Production Tracking and have individual API keys for each. This allows you to track down each of your scripts and the actions they are performing much more accurately in the event logs. .. _setting_up_shotgrid: Adding Script Users =================== -If you'll be using script-based authentication, you need to create a Script entity in ShotGrid. To create a new key, click the + button on the "Scripts" page in the Admin section and give your script a useful name. It's a good idea to add any other relevant information that be be helpful to your other friendly ShotGrid users such as a description of what the script does that is using this key, the email address of the maintainer, etc.: +If you'll be using script-based authentication, you need to create a Script entity in Flow Production Tracking. To create a new key, click the + button on the "Scripts" page in the Admin section and give your script a useful name. It's a good idea to add any other relevant information that be be helpful to your other friendly Flow Production Tracking users such as a description of what the script does that is using this key, the email address of the maintainer, etc.: .. image:: images/scripts_page.png -Once you save your new Script entity, ShotGrid will automatically generate an application key which will act as the script's password. The key will look something like this: ``0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef``. +Once you save your new Script entity, Flow Production Tracking will automatically generate an application key which will act as the script's password. The key will look something like this: ``0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef``. Why have different application keys for different scripts? ========================================================== @@ -42,24 +42,24 @@ We recommend you create a new Script entity (and application key) for each scrip Event Logging ============= -By default, events generated by scripts using an script-based authentication are logged in ShotGrid's event log. You can turn this off by un-checking the "Generate Events" checkbox either in the script detail page or from the main Scripts admin page in ShotGrid. +By default, events generated by scripts using an script-based authentication are logged in Flow Production Tracking's event log. You can turn this off by un-checking the "Generate Events" checkbox either in the script detail page or from the main Scripts admin page in Flow Production Tracking. .. note:: Turning off event logging will also prevent any email notifications from being triggered by your scripts since the email notifier relies on the event log to find events to notify for. -Scripts using user-based authentication will generate events similarly to if you were performing the same actions in the ShotGrid web application, though there is some additional metadata stored in the ``EventLogEntry`` that identifies the event as created from a script acting on behalf of the user. +Scripts using user-based authentication will generate events similarly to if you were performing the same actions in the Flow Production Tracking web application, though there is some additional metadata stored in the ``EventLogEntry`` that identifies the event as created from a script acting on behalf of the user. Why would you want to turn event logging off for scripts? --------------------------------------------------------- -It is an optimization that is not used often, but some users have integration scripts that are pushing data into ShotGrid just for reference, like publishes from their asset management system. This publish data is never changed later, so the data itself has the entire history, and the events would just clutter the event log. The event log can grow very large. So if you have no need to audit the history of what your script does, and it's generating an large amount of event log entries, you may find it's not necessary to create these events. +It is an optimization that is not used often, but some users have integration scripts that are pushing data into Flow Production Tracking just for reference, like publishes from their asset management system. This publish data is never changed later, so the data itself has the entire history, and the events would just clutter the event log. The event log can grow very large. So if you have no need to audit the history of what your script does, and it's generating an large amount of event log entries, you may find it's not necessary to create these events. *********** Permissions *********** -Users and scripts are both bound by the restrictions of their permission role in ShotGrid. The permission role is assigned by the **Permission Role** field for each entity type. +Users and scripts are both bound by the restrictions of their permission role in Flow Production Tracking. The permission role is assigned by the **Permission Role** field for each entity type. -For Scripts, the default permission role is "API Admin User" which allows full access to create, update, and delete entities and fields, including editing the "date created" audit field and creating event log entries. If you have other permission roles for ApiUsers, you can set the default role that will be assigned when a new script is created, in your ShotGrid site preferences. +For Scripts, the default permission role is "API Admin User" which allows full access to create, update, and delete entities and fields, including editing the "date created" audit field and creating event log entries. If you have other permission roles for ApiUsers, you can set the default role that will be assigned when a new script is created, in your Flow Production Tracking site preferences. -When using user-based authentication in your script, it will be bound by the permission role assigned to you in ShotGrid. For example, if you don't have access to edit the status field on Shots, your script won't be able to either. Attempting to perform actions that are prohibited by permissions will raise an appropriate exception. +When using user-based authentication in your script, it will be bound by the permission role assigned to you in Flow Production Tracking. For example, if you don't have access to edit the status field on Shots, your script won't be able to either. Attempting to perform actions that are prohibited by permissions will raise an appropriate exception. .. seealso:: `Permissions Documentation `_ diff --git a/docs/cookbook.rst b/docs/cookbook.rst index 756c44c5b..f69334a7b 100644 --- a/docs/cookbook.rst +++ b/docs/cookbook.rst @@ -29,7 +29,7 @@ and paste any of these into your own scripts. .. rubric:: Working With Files You'll probably be doing some work with files at your studio. This is a deep dive into some of -the inners of how ShotGrid handles files (also called Attachments) and the different ways to link +the inners of how Flow Production Tracking handles files (also called Attachments) and the different ways to link to them. .. toctree:: @@ -39,9 +39,9 @@ to them. .. rubric:: Working With Tasks -Scheduling is a complex beast. ShotGrid can handle lots of different types of functionality around +Scheduling is a complex beast. Flow Production Tracking can handle lots of different types of functionality around scheduling like split tasks, dependencies, and more. These docs walk you through the details of -how ShotGrid thinks when it's handling Task changes and how you can make your scripts do what you +how Flow Production Tracking thinks when it's handling Task changes and how you can make your scripts do what you need to do. .. toctree:: @@ -52,7 +52,7 @@ need to do. .. rubric:: Smart Cut Fields Smart Cut Fields are deprecated in favor of the -`new cut support added in ShotGrid v7.0 `_. +`new cut support added in ShotGrid v7.0 `_. This documentation remains only to support studios who may not have upgraded to the new cut support features. diff --git a/docs/cookbook/attachments.rst b/docs/cookbook/attachments.rst index 5e41f0bd6..e265071e8 100644 --- a/docs/cookbook/attachments.rst +++ b/docs/cookbook/attachments.rst @@ -4,26 +4,26 @@ Details About Working With Files ################################ -The ShotGrid web application stores Files as Attachment entities. You can see these on a Files page, +The Flow Production Tracking web application stores Files as Attachment entities. You can see these on a Files page, or a Files tab on a detail page, for example. You can access Attachments via the API to create and modify uploaded files, url links, and local files, and link them to other entities (Shots, -Versions, etc). This entity works a lot like other entity types within ShotGrid with a few +Versions, etc). This entity works a lot like other entity types within Flow Production Tracking with a few exceptions which are detailed below. .. note:: - If you are simply looking for information about how to upload and link things in ShotGrid, this + If you are simply looking for information about how to upload and link things in Flow Production Tracking, this doc is not for you. Instead look at the :meth:`~shotgun_api3.Shotgun.upload` and :meth:`~shotgun_api3.Shotgun.upload_thumbnail` methods. This doc describes the detailed structure of the Attachment entities that represent files - in ShotGrid and how to interact with them. If that sounds cool too, then read on! + in Flow Production Tracking and how to interact with them. If that sounds cool too, then read on! .. versionadded:: 3.0.3 ***************** Default structure ***************** -The following is a list of the default fields that ShotGrid creates for Attachments. Your server +The following is a list of the default fields that Flow Production Tracking creates for Attachments. Your server instance may look slightly different depending on your own customizations. Many of these fields are optional and some are automatically filled in. These exceptions are listed below in the descriptions of each field. @@ -46,7 +46,7 @@ descriptions of each field. the size of the file in bytes. - **id** (:obj:`int`): - The internal ShotGrid id for this Attachment entity. + The internal Flow Production Tracking id for this Attachment entity. - **attachment_links** (:obj:`list`): A list of entity dictionaries used for linking Attachments to multiple entities. @@ -74,7 +74,7 @@ descriptions of each field. List of tags (as strings) that are currently assigned to the Attachment. - **image** (:obj:`str`): - The url location of the thumbnail image assigned to this Attachment. For uploads, ShotGrid + The url location of the thumbnail image assigned to this Attachment. For uploads, Flow Production Tracking automatically tries to create a thumbnail from the file. See :ref:`interpreting_image_field_strings`. Alternatively, you can assign your own thumbnail to an Attachment using the :meth:`~shotgun_api3.Shotgun.upload_thumbnail` method. @@ -94,8 +94,8 @@ Depending on the type of file the Attachment entity is representing, the value o will vary. - **Uploads** - Designated by ``link_type: 'upload'``, this represents a file that was uploaded to ShotGrid. - Uploading files to ShotGrid can be done using the :meth:`~shotgun_api3.Shotgun.upload` method. + Designated by ``link_type: 'upload'``, this represents a file that was uploaded to Flow Production Tracking. + Uploading files to Flow Production Tracking can be done using the :meth:`~shotgun_api3.Shotgun.upload` method. You cannot create an Attachment with an uploaded file directly. :: @@ -174,14 +174,14 @@ Updating Attachments ******************** You cannot modify the ``this_file`` field after you create an Attachment. If you need to provide a different file, you will have to create a new Attachment entity. Otherwise, the process for -updating Attachments is exactly like updating other entity types in ShotGrid and is the same for all +updating Attachments is exactly like updating other entity types in Flow Production Tracking and is the same for all Attachment types. See :meth:`~shotgun_api3.Shotgun.update` for more info. ******************** Deleting Attachments ******************** -The process of deleting an Attachment is just like other entities in ShotGrid. See +The process of deleting an Attachment is just like other entities in Flow Production Tracking. See :meth:`~shotgun_api3.Shotgun.delete` for more info. .. _local_files: @@ -190,14 +190,14 @@ The process of deleting an Attachment is just like other entities in ShotGrid. S Working With Local File Types ***************************** -We added support for linking to local files in the UI in ShotGrid Server v2.1. This doc covers how +We added support for linking to local files in the UI in Flow Production Tracking Server v2.1. This doc covers how to work with these local file links using the API. Requirements ============ - Python API v3.0.3+ -- ShotGrid Server v2.1.10+ +- Flow Production Tracking Server v2.1.10+ Structure of Local File Values ============================== @@ -275,7 +275,7 @@ Returns:: Creating & Updating Local file Fields ===================================== -When setting a file/link field value to a local file, only the ``local_path`` is mandatory. ShotGrid +When setting a file/link field value to a local file, only the ``local_path`` is mandatory. Flow Production Tracking will automatically select the appropriate matching local storage for your file based on the path. You can optionally specify the ``name`` and ``content_type`` fields if you wish to override their defaults. Any other keys that are provided will be ignored. @@ -289,7 +289,7 @@ defaults. Any other keys that are provided will be ignored. Optional display name of the local file. This is set to the filename by default. * **local_path** :obj:`str`: - The full local path to the file. ShotGrid will find the LocalStorage + The full local path to the file. Flow Production Tracking will find the LocalStorage that has the most specific match to this path and automatically assign that LocalStorage to the file. @@ -315,7 +315,7 @@ Returns:: 'url': 'file:///Users/kp/Movies/testing/test_movie_002.mov'}, 'type': 'Version'}] -The ``content_type`` was assigned a best-guess value based on the file extension. ShotGrid selected +The ``content_type`` was assigned a best-guess value based on the file extension. Flow Production Tracking selected the most appropriate specific LocalStorage match and assigned it to local_storage automatically. Un-setting local file field values diff --git a/docs/cookbook/examples/ami_version_packager.rst b/docs/cookbook/examples/ami_version_packager.rst index 3ac7917d0..5d3035014 100644 --- a/docs/cookbook/examples/ami_version_packager.rst +++ b/docs/cookbook/examples/ami_version_packager.rst @@ -20,7 +20,7 @@ It is intended to be used in conjunction with the script dicussed in :ref:`ami_h """ version_packager.py - This example script is meant to be run from an ActionMenuItem in ShotGrid. The menu item uses a custom + This example script is meant to be run from an ActionMenuItem in Flow Production Tracking. The menu item uses a custom protocol in order to launch this script, and is followed by the action 'package4client'. So the full url would be something like launchme://package4client?.... See: https://developer.shotgridsoftware.com/python-api/cookbook/examples/ami_handler.html @@ -29,9 +29,9 @@ It is intended to be used in conjunction with the script dicussed in :ref:`ami_h POST variables. For more information about it and accessing the variables in the ActionMenuItem POST request, See: http://developer.shotgridsoftware.com/python-api/examples/ami_handler - The purpose of this script is to download attachment files from ShotGrid, create an archive of them + The purpose of this script is to download attachment files from Flow Production Tracking, create an archive of them and copy them to a specified directory. You can invoke it with the following minimal example to connect - to ShotGrid, download any file that exists in the specified field ('sg_qt') for each selected_id passed from the + to Flow Production Tracking, download any file that exists in the specified field ('sg_qt') for each selected_id passed from the ActionMenu. Then it will create a single archive of the files and move it to the specified directory ('/path/where/i/want/to/put/the/archive/'). The archive is named with the Project Name, timestamp, and user login who ran the ActionMenuItem ('Demo_Project_2010-04-29-172210_kp.tar.gz'): @@ -59,7 +59,7 @@ It is intended to be used in conjunction with the script dicussed in :ref:`ami_h # --------------------------------------------------------------------------------------------- # Variables # --------------------------------------------------------------------------------------------- - # ShotGrid server auth info + # Flow Production Tracking server auth info shotgun_conf = { 'url':'https://my-site.shotgrid.autodesk.com', 'name':'YOUR_SCRIPT_NAME_HERE', @@ -79,7 +79,7 @@ It is intended to be used in conjunction with the script dicussed in :ref:`ami_h # ---------------------------------------------- - # Generic ShotGrid Exception Class + # Generic Flow Production Tracking Exception Class # ---------------------------------------------- class ShotgunException(Exception): pass @@ -125,7 +125,7 @@ It is intended to be used in conjunction with the script dicussed in :ref:`ami_h attachment_id = extract_attachment_id(attachment) if type(attachment_id) != int: return None - # download the attachment file from ShotGrid and write it to local disk + # download the attachment file from Flow Production Tracking and write it to local disk logger.info("Downloading Attachment #%s" % (attachment_id)) stream = sg.download_attachment(attachment_id) try: diff --git a/docs/cookbook/examples/basic_create_shot.rst b/docs/cookbook/examples/basic_create_shot.rst index 5c70585cf..4a9ece5f6 100644 --- a/docs/cookbook/examples/basic_create_shot.rst +++ b/docs/cookbook/examples/basic_create_shot.rst @@ -27,7 +27,7 @@ This will create a new Shot named "100_010" in the Project "Gunslinger" (which h - ``data`` is a list of key/value pairs where the key is the column name to update and the value is the the value to set. -- ``sg`` is the ShotGrid API instance you created in :ref:`example_sg_instance`. +- ``sg`` is the Flow Production Tracking API instance you created in :ref:`example_sg_instance`. - ``create()`` is the :meth:`shotgun_api3.Shotgun.create` API method we are calling. We pass in the entity type we're searching for and the data we're setting. @@ -44,11 +44,11 @@ The variable ``result`` now contains a dictionary hash with the Shot information 'type': 'Shot' } -In addition, ShotGrid has returned the ``id`` that it has assigned to the Shot, as well as a +In addition, Flow Production Tracking has returned the ``id`` that it has assigned to the Shot, as well as a ``type`` value. ``type`` is provided for convenience simply to help you identify what entity type -this dictionary represents. It does not correspond to any field in ShotGrid. +this dictionary represents. It does not correspond to any field in Flow Production Tracking. -ShotGrid will *always* return the ``id`` and ``type`` keys in the dictionary when there are results +Flow Production Tracking will *always* return the ``id`` and ``type`` keys in the dictionary when there are results representing an entity. The Complete Example @@ -66,7 +66,7 @@ The Complete Example # -------------------------------------- # Globals # -------------------------------------- - # make sure to change this to match your ShotGrid server and auth credentials. + # make sure to change this to match your Flow Production Tracking server and auth credentials. SERVER_PATH = "https://my-site.shotgrid.autodesk.com" SCRIPT_NAME = 'my_script' SCRIPT_KEY = '27b65d7063f46b82e670fe807bd2b6f3fd1676c1' diff --git a/docs/cookbook/examples/basic_create_shot_task_template.rst b/docs/cookbook/examples/basic_create_shot_task_template.rst index 4144bf460..0fa3b0828 100644 --- a/docs/cookbook/examples/basic_create_shot_task_template.rst +++ b/docs/cookbook/examples/basic_create_shot_task_template.rst @@ -1,6 +1,6 @@ Create a Shot with a Task Template ================================== -Creating a new Shot with a Task Template is just like linking it to another entity, but ShotGrid will apply the Task Template in the background and create the appropriate Tasks on the Shot for you. +Creating a new Shot with a Task Template is just like linking it to another entity, but Flow Production Tracking will apply the Task Template in the background and create the appropriate Tasks on the Shot for you. Find the Task Template ---------------------- @@ -31,7 +31,7 @@ Now we can create the Shot with the link to the ``TaskTemplate`` to apply. result = sg.create('Shot', data) This will create a new Shot named "100_010" linked to the TaskTemplate "3D Shot Template" and -ShotGrid will then create the Tasks defined in the template and link them to the Shot you just +Flow Production Tracking will then create the Tasks defined in the template and link them to the Shot you just created. - ``data`` is a list of key/value pairs where the key is the column name to update and the value is @@ -65,7 +65,7 @@ If we now search for the Tasks linked to the Shot, we'll find the Tasks that mat tasks = sg.find('Task', filters=[['entity', 'is', result]]) .. note:: You can use an entity dictionary that was returned from the API in a filter as we have - done above. ShotGrid will only look at the ``id`` and ``type`` keys and will ignore the rest. + done above. Flow Production Tracking will only look at the ``id`` and ``type`` keys and will ignore the rest. This is a handy way to pass around entity dictionaries without having to reformat them. Now the ``tasks`` variable contains the following:: diff --git a/docs/cookbook/examples/basic_create_version_link_shot.rst b/docs/cookbook/examples/basic_create_version_link_shot.rst index bdec9ef46..71e6f3e1e 100644 --- a/docs/cookbook/examples/basic_create_version_link_shot.rst +++ b/docs/cookbook/examples/basic_create_version_link_shot.rst @@ -1,6 +1,6 @@ Create a Version Linked to a Shot ================================= -You've just created a sweet new Version of your shot. Now you want to update ShotGrid and create a +You've just created a sweet new Version of your shot. Now you want to update Flow Production Tracking and create a new ``Version`` entity linked to the Shot. Find the Shot @@ -28,7 +28,7 @@ variable from the previous search) and the Task Name, which maps to the ``conten .. note:: Linking a Task to the Version is good practice. By doing so it is easy for users to see at what stage a particular Version was created, and opens up other possibilities for tracking - in ShotGrid. We highly recommend doing this whenever possible. + in Flow Production Tracking. We highly recommend doing this whenever possible. Create the Version ------------------ diff --git a/docs/cookbook/examples/basic_delete_shot.rst b/docs/cookbook/examples/basic_delete_shot.rst index 5041ee269..5275735d6 100644 --- a/docs/cookbook/examples/basic_delete_shot.rst +++ b/docs/cookbook/examples/basic_delete_shot.rst @@ -3,7 +3,7 @@ Delete A Shot Calling :meth:`~shotgun_api3.Shotgun.delete` -------------------------------------------- -Deleting an entity in ShotGrid is pretty straight-forward. No extraneous steps required.:: +Deleting an entity in Flow Production Tracking is pretty straight-forward. No extraneous steps required.:: result = sg.delete("Shot", 40435) @@ -28,7 +28,7 @@ The Complete Example # -------------------------------------- # Globals # -------------------------------------- - # make sure to change this to match your ShotGrid server and auth credentials. + # make sure to change this to match your Flow Production Tracking server and auth credentials. SERVER_PATH = "https://my-site.shotgrid.autodesk.com" SCRIPT_NAME = 'my_script' SCRIPT_KEY = '27b65d7063f46b82e670fe807bd2b6f3fd1676c1' diff --git a/docs/cookbook/examples/basic_find_shot.rst b/docs/cookbook/examples/basic_find_shot.rst index 19fed7cf9..88c8c81e0 100644 --- a/docs/cookbook/examples/basic_find_shot.rst +++ b/docs/cookbook/examples/basic_find_shot.rst @@ -14,7 +14,7 @@ Pretty simple right? Well here's a little more insight into what's going on. - ``filters`` is an list of filter conditions. In this example we are filtering for Shots where the ``id`` column is **40435**. -- ``sg`` is the ShotGrid API instance. +- ``sg`` is the Flow Production Tracking API instance. - ``find_one()`` is the :meth:`~shotgun_api3.Shotgun.find_one` API method we are calling. We provide it with the entity type we're searching for and our filters. @@ -26,7 +26,7 @@ So what does this return? The variable result now contains:: {'type': 'Shot','id': 40435} By default, :meth:`~shotgun_api3.Shotgun.find_one` returns a single dictionary object with -the ``type`` and ``id`` fields. So in this example, we found a Shot matching that id, and ShotGrid +the ``type`` and ``id`` fields. So in this example, we found a Shot matching that id, and Flow Production Tracking returned it as a dictionary object with ``type`` and ``id`` keys . How do we know that result contains the Shot dictionary object? You can trust us... but just to be @@ -52,7 +52,7 @@ The Complete Example # -------------------------------------- # Globals # -------------------------------------- - # make sure to change this to match your ShotGrid server and auth credentials. + # make sure to change this to match your Flow Production Tracking server and auth credentials. SERVER_PATH = "https://my-site.shotgrid.autodesk.com" SCRIPT_NAME = 'my_script' SCRIPT_KEY = '27b65d7063f46b82e670fe807bd2b6f3fd1676c1' diff --git a/docs/cookbook/examples/basic_sg_instance.rst b/docs/cookbook/examples/basic_sg_instance.rst index fed8a6c51..b39c78432 100644 --- a/docs/cookbook/examples/basic_sg_instance.rst +++ b/docs/cookbook/examples/basic_sg_instance.rst @@ -1,11 +1,11 @@ .. _example_sg_instance: -Create a ShotGrid API instance -============================= +Create a Flow Production Tracking API instance +============================================== -This example shows you how to establish your initial connection to ShotGrid using script-based -authentication. ``sg`` represents your ShotGrid API instance. Be sure you've read -:ref:`Setting Up ShotGrid for API Access `. +This example shows you how to establish your initial connection to Flow Production Tracking using script-based +authentication. ``sg`` represents your Flow Production Tracking API instance. Be sure you've read +:ref:`Setting Up Flow Production Tracking for API Access `. :: import pprint # Useful for debugging @@ -22,5 +22,5 @@ authentication. ``sg`` represents your ShotGrid API instance. Be sure you've rea # sg connection object pprint.pprint([symbol for symbol in sorted(dir(sg)) if not symbol.startswith('_')]) -For further information on what you can do with this ShotGrid object you can read the +For further information on what you can do with this Flow Production Tracking object you can read the :ref:`API reference `. \ No newline at end of file diff --git a/docs/cookbook/examples/basic_update_shot.rst b/docs/cookbook/examples/basic_update_shot.rst index 5d1b4ff9b..52e57e70e 100644 --- a/docs/cookbook/examples/basic_update_shot.rst +++ b/docs/cookbook/examples/basic_update_shot.rst @@ -17,7 +17,7 @@ This will update the ``description`` and the ``sg_status_list`` fields for the S - ``data`` is a list of key/value pairs where the key is the field name to update and the value to update it to. -- ``sg`` is the ShotGrid API instance. +- ``sg`` is the Flow Production Tracking API instance. - ``update()`` is the :meth:`shotgun_api3.Shotgun.update` API method we are calling. We provide it with the entity type we're updating, the ``id`` of the entity, and the data we're updating it with. @@ -33,11 +33,11 @@ The variable ``result`` now contains the Shot object that with the updated value 'id': 40435 } -In addition, ShotGrid has returned the ``id`` for the Shot, as well as a ``type`` value. ``type`` +In addition, Flow Production Tracking has returned the ``id`` for the Shot, as well as a ``type`` value. ``type`` is provided for convenience simply to help you identify what entity type this dictionary represents. -It does not correspond to any field in ShotGrid. +It does not correspond to any field in Flow Production Tracking. -ShotGrid will *always* return the ``id`` and ``type`` keys in the dictionary when there are results +Flow Production Tracking will *always* return the ``id`` and ``type`` keys in the dictionary when there are results representing an entity. The Complete Example @@ -55,7 +55,7 @@ The Complete Example # -------------------------------------- # Globals # -------------------------------------- - # make sure to change this to match your ShotGrid server and auth credentials. + # make sure to change this to match your Flow Production Tracking server and auth credentials. SERVER_PATH = "https://my-site.shotgrid.autodesk.com" SCRIPT_NAME = 'my_script' SCRIPT_KEY = '27b65d7063f46b82e670fe807bd2b6f3fd1676c1' diff --git a/docs/cookbook/examples/basic_upload_thumbnail_version.rst b/docs/cookbook/examples/basic_upload_thumbnail_version.rst index ed8b51827..2ae399d5f 100644 --- a/docs/cookbook/examples/basic_upload_thumbnail_version.rst +++ b/docs/cookbook/examples/basic_upload_thumbnail_version.rst @@ -1,10 +1,10 @@ Upload a Thumbnail for a Version ================================ -So you've created a new Version of a Shot, and you've updated ShotGrid, but now you want to upload a +So you've created a new Version of a Shot, and you've updated Flow Production Tracking, but now you want to upload a beauty frame to display as the thumbnail for your Version. We'll assume you already have the image made (located on your machine at ``/v1/gun/s100/010/beauties/anim/100_010_animv1.jpg``) . And since -you've just created your Version in ShotGrid, you know its ``id`` is **214**. +you've just created your Version in Flow Production Tracking, you know its ``id`` is **214**. .. note:: If you upload a movie file or image to the ``sg_uploaded_movie`` field and you have transcoding enabled on your server (the default for hosted sites), a thumbnail will be @@ -18,9 +18,9 @@ Upload the Image using :meth:`~shotgun_api3.Shotgun.upload_thumbnail` sg.upload_thumbnail("Version", 214, "/v1/gun/s100/010/beauties/anim/100_010_animv1.jpg") -ShotGrid will take care of resizing the thumbnail for you. If something does go wrong, an exception +Flow Production Tracking will take care of resizing the thumbnail for you. If something does go wrong, an exception will be thrown and you'll see the error details. .. note:: The result returned by :meth:`~shotgun_api3.Shotgun.upload_thumbnail` is an integer - representing the id of a special ``Attachment`` entity in ShotGrid. Working with Attachments + representing the id of a special ``Attachment`` entity in Flow Production Tracking. Working with Attachments is beyond the scope of this example. :) \ No newline at end of file diff --git a/docs/cookbook/examples/svn_integration.rst b/docs/cookbook/examples/svn_integration.rst index c0c3730b5..9a877b322 100644 --- a/docs/cookbook/examples/svn_integration.rst +++ b/docs/cookbook/examples/svn_integration.rst @@ -4,10 +4,10 @@ Subversion (SVN) Integration ############################ -Integrating ShotGrid with Subversion consists of two basic parts: +Integrating Flow Production Tracking with Subversion consists of two basic parts: - Setup a post-commit hook in Subversion. -- Create a ShotGrid API script to create the Revision in ShotGrid. This script will be called by +- Create a Flow Production Tracking API script to create the Revision in Flow Production Tracking. This script will be called by the post-commit hook. **************** @@ -21,7 +21,7 @@ To setup the post-commit hook: as a starting point for the real thing. - Create your very own executable script, and save it in the same ``hooks`` folder, name it ``post-commit``, and give it executable permission. -- In your ``post-commit`` script, invoke your ShotGrid API script. +- In your ``post-commit`` script, invoke your Flow Production Tracking API script. If this is entirely new to you, we highly suggest reading up on the topic. O'Reilly has `a free online guide for Subversion 1.5 and 1.6 @@ -55,14 +55,14 @@ Explanation of selected lines our ``REPOS`` and ``REV`` values, first with the ``author``, and then with ``log`` subcommand. These are actually the first two original lines of code - everything else to this point was pre-written already in the ``post-commit.tmpl`` file. nice :) -- line ``10``: This is the absolute path to our ShotGrid API Script. +- line ``10``: This is the absolute path to our Flow Production Tracking API Script. -****************** -ShotGrid API Script -****************** +*********************************** +Flow Production Tracking API Script +*********************************** -This script will create the Revision and populate it with some metadata using the ShotGrid Python -API. It will create our Revision in ShotGrid along with the author, comment, and because we use +This script will create the Revision and populate it with some metadata using the Flow Production Tracking Python +API. It will create our Revision in Flow Production Tracking along with the author, comment, and because we use Trac (a web-based interface for Subversion), it will also populate a URL field with a clickable link to the Revision. @@ -105,7 +105,7 @@ link to the Revision. # Set the Trac path for this specific revision revision_url = REVISIONS_PATH + revision_code - # Validate that author is a valid ShotGrid HumanUser + # Validate that author is a valid Flow Production Tracking HumanUser result = sg.find_one("HumanUser", [['login', 'is', author]]) if result: # Create Revision @@ -121,33 +121,33 @@ link to the Revision. # Send error message if valid HumanUser is not found else: - print("Unable to find valid ShotGrid User with login: "+author+", Revision not created in ShotGrid.") + print("Unable to find a valid Flow Production Tracking User with login: {}, Revision not created in Flow Production Tracking.".format(author)) Explanation of selected lines: ============================== -- line ``14``: This should be the URL to your instance of ShotGrid. +- line ``14``: This should be the URL to your instance of Flow Production Tracking. - lines ``15-16``: Make sure you get these values from the "Scripts" page in the Admin section of - the ShotGrid web application. If you're not sure how to do this, check out :doc:`authentication`. + the Flow Production Tracking web application. If you're not sure how to do this, check out :doc:`authentication`. - line ``17``: This is the address of Trac, our web-based interface that we use with Subversion. You may use a different interface, or none at all, so feel free to adjust this line or ignore it as your case may be. -- line ``18``: Every Revision in ShotGrid must have a Project, which is passed to the API as a +- line ``18``: Every Revision in Flow Production Tracking must have a Project, which is passed to the API as a dictionary with two keys, the ``type`` and the ``id``. Of course the ``type`` value will always remain ``Project`` (case sensitive), but the ``id`` will change by Project. To find out the - ``id`` of your Project, go to the Projects page in the ShotGrid web application, locate the + ``id`` of your Project, go to the Projects page in the Flow Production Tracking web application, locate the Project where you want your Revisions created, and then locate its ``id`` field (which you may need to display - if you don't see it, right click on any column header then select "Insert Column" > "Id"). Note that for this example we assume that all Revisions in this Subversion repository will belong to the same Project. - lines ``28-31``: Grab the values from the objects that were left for us in the environment. - line ``34``: Add the Revision number to complete the path of our Trac url. -- line ``37``: Make sure that a valid User exists in ShotGrid. In our example, we assume that our - Users' ShotGrid logins match their Subversion names. If the user exists in ShotGrid, that +- line ``37``: Make sure that a valid User exists in Flow Production Tracking. In our example, we assume that our + Users' Flow Production Tracking logins match their Subversion names. If the user exists in Flow Production Tracking, that user's ``id`` will be returned as ``result['id']``, which we will need later on in line 46. -- lines ``40-48``: Use all the meta data we've gathered to create a Revision in ShotGrid. If none +- lines ``40-48``: Use all the meta data we've gathered to create a Revision in Flow Production Tracking. If none of these lines make any sense, check out more on the :meth:`~shotgun_api3.Shotgun.create` method here. Line 41 deserves special mention: notice that we define a dictionary called ``url`` that has three important keys: ``content_type``, ``url``, and ``name``, and we then pass this in as @@ -164,7 +164,7 @@ My post-commit script is simply not running. I can run it manually, but commits Make sure that the script is has explicitly been made executable and that all users who will invoke it have appropriate permissions for the script and that folders going back to root. -My ShotGrid API script is not getting called by the post-commit hook. -==================================================================== +My Flow Production Tracking API script is not getting called by the post-commit hook. +===================================================================================== Make sure that the script is called using its absolute path. diff --git a/docs/cookbook/smart_cut_fields.rst b/docs/cookbook/smart_cut_fields.rst index 23d8669da..0ee74d189 100644 --- a/docs/cookbook/smart_cut_fields.rst +++ b/docs/cookbook/smart_cut_fields.rst @@ -5,7 +5,7 @@ Smart Cut Fields ################ .. warning:: - Smart Cut Fields should be considered deprecated. ShotGrid v7.0, introduced a new version of + Smart Cut Fields should be considered deprecated. ShotGrid v7.0, introduced a new version of cut support. `Read the Cut Support Documentation here `_. If you want to work with 'smart' cut fields through the API, your script should use a corresponding diff --git a/docs/cookbook/tasks/split_tasks.rst b/docs/cookbook/tasks/split_tasks.rst index ade069f99..45dfc303f 100644 --- a/docs/cookbook/tasks/split_tasks.rst +++ b/docs/cookbook/tasks/split_tasks.rst @@ -5,7 +5,7 @@ Split Tasks ########### Split tasks can be created and edited via the API but must comply to some rules. Before going -further, a good understanding of :ref:`how ShotGrid handles task dates is useful `. +further, a good understanding of :ref:`how Flow Production Tracking handles task dates is useful `. ******** Overview diff --git a/docs/cookbook/tasks/task_dependencies.rst b/docs/cookbook/tasks/task_dependencies.rst index 48a09e686..a5cfc20be 100644 --- a/docs/cookbook/tasks/task_dependencies.rst +++ b/docs/cookbook/tasks/task_dependencies.rst @@ -5,7 +5,7 @@ Task Dependencies ################# Task dependencies work the same way in the API as they do in the UI. You can filter and sort on -any of the fields. For information about Task Dependencies in ShotGrid, check out the `main +any of the fields. For information about Task Dependencies in Flow Production Tracking, check out the `main documentation page on our support site `_ @@ -232,7 +232,7 @@ Our Tasks now look like this:: ... Because the "Anm" Task ``start_date`` depends on the ``due_date`` of the "Layout" Task, this -change creates a dependency violation. The update succeeds, but ShotGrid has also set the +change creates a dependency violation. The update succeeds, but Flow Production Tracking has also set the ``dependency_violation`` field to ``True`` and has also updated the ``pinned`` field to ``True``. The ``pinned`` field simply means that if the upstream Task(s) are moved, the "Anm" Task will no @@ -315,7 +315,7 @@ Updating the ``pinned`` field on a Task with a Dependency Violation Let's go back a couple of steps to where our "Anm" Task had a Dependency Violation because we had moved the Start Date up before the "Layout" Task End Date. Remember that the ``pinned`` field -was also ``True``. If we simply update the ``pinned`` field to be ``False``, ShotGrid will also +was also ``True``. If we simply update the ``pinned`` field to be ``False``, Flow Production Tracking will also automatically update the Task dates to satisfy the upstream dependencies and reset the ``dependency_violation`` value to ``False``:: @@ -349,7 +349,7 @@ Our Tasks now look like this:: ... -Notice by updating ``pinned`` to ``False``, ShotGrid also updated the ``start_date`` and +Notice by updating ``pinned`` to ``False``, Flow Production Tracking also updated the ``start_date`` and ``due_date`` fields of our "Anm" Task so it will satisfy the upstream Task dependencies. And since that succeeded, the ``dependency_violation`` field has also been set to ``False`` diff --git a/docs/cookbook/tasks/updating_tasks.rst b/docs/cookbook/tasks/updating_tasks.rst index 3f8f9d5b7..97eb8c7b7 100644 --- a/docs/cookbook/tasks/updating_tasks.rst +++ b/docs/cookbook/tasks/updating_tasks.rst @@ -1,11 +1,11 @@ .. _updating_tasks: -####################################### -Updating Task Dates: How ShotGrid Thinks -####################################### +######################################################## +Updating Task Dates: How Flow Production Tracking Thinks +######################################################## When updating Task dates in an API update() request, there is no specified order to the values that -are passed in. ShotGrid also does automatic calculation of the``start_date``,``due_date``, and ``duration`` fields for convenience. In order to clarify how updates are handled by ShotGrid we are +are passed in. Flow Production Tracking also does automatic calculation of the``start_date``,``due_date``, and ``duration`` fields for convenience. In order to clarify how updates are handled by Flow Production Tracking we are providing some general rules below and examples of what will happen when you make updates to your Tasks. @@ -16,12 +16,12 @@ General Rules - Updating the ``start_date`` automatically updates the ``due_date`` (``duration`` remains constant) - Updating the ``due_date`` automatically updates the ``duration`` (``start_date`` remains constant) - Updating the ``duration`` automatically updates the ``due_date`` (``start_date`` remains constant) -- When updating Task values, ShotGrid sets schedule fields (``milestone``, ``duration``, +- When updating Task values, Flow Production Tracking sets schedule fields (``milestone``, ``duration``, ``start_date``, ``due_date``) after all other fields, because the Project and Task Assignees affect schedule calculations. - If ``start_date`` and ``due_date`` are both set, ``duration`` is ignored (``duration`` can often be wrong since it's easy to calculate scheduling incorrectly). -- If both ``start_date`` and ``due_date`` are provided, ShotGrid sets ``start_date`` before +- If both ``start_date`` and ``due_date`` are provided, Flow Production Tracking sets ``start_date`` before ``due_date``. - Set ``milestone`` before other schedule fields (because ``start_date``, ``due_date``, and ``duration`` get lost if ``milestone`` is not set to ``False`` first) @@ -39,7 +39,7 @@ Examples The following examples show what the resulting Task object will look like after being run on the initial Task object listed under the header of each section. -The ``duration`` values in the following examples assume your ShotGrid instance is set to +The ``duration`` values in the following examples assume your Flow Production Tracking instance is set to 10-hour work days. If your server is configured with a different setting, the ``duration`` values will vary. diff --git a/docs/cookbook/tutorials.rst b/docs/cookbook/tutorials.rst index 18235db89..99f56da02 100644 --- a/docs/cookbook/tutorials.rst +++ b/docs/cookbook/tutorials.rst @@ -3,7 +3,7 @@ Examples ######## Here's a list of various simple tutorials to walk through that should provide you with a good base -understanding of how to use the ShotGrid API and what you can do with it. +understanding of how to use the Flow Production Tracking API and what you can do with it. ***** Basic diff --git a/docs/cookbook/usage_tips.rst b/docs/cookbook/usage_tips.rst index 389eb5227..620a96426 100644 --- a/docs/cookbook/usage_tips.rst +++ b/docs/cookbook/usage_tips.rst @@ -2,7 +2,7 @@ API Usage Tips ############## -Below is a list of helpful tips when using the ShotGrid API. We have tried to make the API very +Below is a list of helpful tips when using the Flow Production Tracking API. We have tried to make the API very simple to use with predictable results while remaining a powerful tool to integrate with your pipeline. However, there's always a couple of things that crop up that our users might not be aware of. Those are the types of things you'll find below. We'll be adding to this document over @@ -28,7 +28,7 @@ Don't:: *************** Multi-threading *************** -The ShotGrid API is not thread-safe. If you want to do threading we strongly suggest that you use +The Flow Production Tracking API is not thread-safe. If you want to do threading we strongly suggest that you use one connection object per thread and not share the connection. .. _entity-fields: @@ -105,7 +105,7 @@ example of a field that resides on the connection entity between Playlists and V connection entity is appropriately called `PlaylistVersionConnection`. Because any Version can exist in multiple Playlists, the sort order isn't specific to the Version, it's specific to each _instance_ of the Version in a Playlist. These instances are tracked using connection -entities in Shtogun and are accessible just like any other entity type in ShotGrid. +entities in Shtogun and are accessible just like any other entity type in Flow Production Tracking. To find information about your Versions in the Playlist "Director Review" (let's say it has an ``id`` of 4). We'd run a query like so:: @@ -175,9 +175,9 @@ We can pull in field values from the linked Playlist and Version entities using entity. The field we are interested on the Version is ``code``. Put those together with our f riend the dot and we have ``version.Version.code``. -******************************************* -ShotGrid UI fields not available via the API -******************************************* +************************************************************ +Flow Production Tracking UI fields not available via the API +************************************************************ Summary type fields like Query Fields and Pipeline Step summary fields are currently only available via the UI. Some other fields may not work as expected through the API because they are "display @@ -244,14 +244,14 @@ To see the logging output in stdout, define a streamhandler in your script:: import shotgun_api3 as shotgun logging.basicConfig(level=logging.DEBUG) -To write logging output from the ShotGrid API to a file, define a file handler in your script:: +To write logging output from the Flow Production Tracking API to a file, define a file handler in your script:: import logging import shotgun_api3 as shotgun logging.basicConfig(level=logging.DEBUG, filename='/path/to/your/log') To suppress the logging output from the API in a script which uses logging, set the level of the -ShotGrid logger to a higher level:: +Flow Production Tracking logger to a higher level:: import logging import shotgun_api3 as shotgun @@ -267,7 +267,7 @@ Optimizations Combining Related Queries ========================= Reducing round-trips for data via the API can significantly improve the speed of your application. -Much like "Bubble Fields" / "Field Hopping" in the UI, we can poll ShotGrid for data on the fields +Much like "Bubble Fields" / "Field Hopping" in the UI, we can poll Flow Production Tracking for data on the fields of entities linked to our main query, both as a part of the query parameters as well as in the data returned. diff --git a/docs/images/scripts_page.png b/docs/images/scripts_page.png index fdbc3c38d664f8c8aa783f57a9420812fcd64006..d53da1cac240f19eda0b3649f6699eef53fb3bbd 100644 GIT binary patch literal 72478 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
    I^~4DL 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 literal 73074 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

      uRH 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 r@x64zBlC1ZQ@wT6`) .. image:: https://img.shields.io/badge/shotgun-api-blue.svg -ShotGrid provides a simple Python-based API for accessing ShotGrid and integrating with other tools. -The ShotGrid API allows users to integrate their tools with ShotGrid very easily. Using this simple -but powerful python module , you can quickly get your scripts integrated with ShotGrid's CRUD-based +Flow Production Tracking provides a simple Python-based API for accessing Flow Production Tracking and integrating with other tools. +The Flow Production Tracking API allows users to integrate their tools with Flow Production Tracking very easily. Using this simple +but powerful python module , you can quickly get your scripts integrated with Flow Production Tracking's CRUD-based API. Because the needs of every studio can prove to be very different, we don't include a lot of "automation" or "smarts" in our API. We have kept it pretty low-level and leave most of those decisions to you. The API is powerful enough you can write your own "smarts" in a wrapper on top -of the ShotGrid API. +of the Flow Production Tracking API. .. _pythonoverviewvideo: diff --git a/docs/installation.rst b/docs/installation.rst index c40f61560..b082b1669 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -9,10 +9,10 @@ Minimum Requirements - Python 3.7 .. note:: - Some features of the API are only supported by more recent versions of the ShotGrid server. + Some features of the API are only supported by more recent versions of the Flow Production Tracking server. These features are added to the Python API in a backwards compatible way so that existing scripts will continue to function as expected. Accessing a method that is not supported for - your version of ShotGrid will raise an appropriate exception. In general, we attempt to + your version of Flow Production Tracking will raise an appropriate exception. In general, we attempt to document these where possible. ****************************** diff --git a/docs/reference.rst b/docs/reference.rst index e7558f65c..c4da96caf 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -26,9 +26,9 @@ Shotgun() .. autoclass:: Shotgun :show-inheritance: -*************** -ShotGrid Methods -*************** +******************************** +Flow Production Tracking Methods +******************************** The majority of functionality is contained within the :class:`~shotgun_api3.Shotgun` class. The documentation for all of the methods you'll need in your scripts lives in here. @@ -91,7 +91,7 @@ The documentation for all of the methods you'll need in your scripts lives in he Shotgun.followers Shotgun.following -.. rubric:: Working with the ShotGrid Schema and Preferences +.. rubric:: Working with the Flow Production Tracking Schema and Preferences .. autosummary:: :nosignatures: @@ -109,7 +109,7 @@ The documentation for all of the methods you'll need in your scripts lives in he Connection & Authentication =========================== -These methods are used for connecting and authenticating with your ShotGrid server. Most of +These methods are used for connecting and authenticating with your Flow Production Tracking server. Most of this is done automatically when you instantiate your instance. But if you need finer-grain control, these methods are available. @@ -169,7 +169,7 @@ Methods that handle uploading and downloading files including thumbnails. Activity Stream =============== -Methods that relate to the activity stream and following of entities in ShotGrid. +Methods that relate to the activity stream and following of entities in Flow Production Tracking. .. automethod:: Shotgun.activity_stream_read .. automethod:: Shotgun.follow @@ -177,10 +177,10 @@ Methods that relate to the activity stream and following of entities in ShotGrid .. automethod:: Shotgun.followers .. automethod:: Shotgun.following -Working with the ShotGrid Schema -=============================== +Working with the Flow Production Tracking Schema +================================================ -Methods allow you to introspect and modify the ShotGrid schema. +Methods allow you to introspect and modify the Flow Production Tracking schema. .. automethod:: Shotgun.schema_entity_read .. automethod:: Shotgun.schema_field_read @@ -195,7 +195,7 @@ Methods allow you to introspect and modify the ShotGrid schema. Exceptions ********** -These are the various exceptions that the ShotGrid API will raise. +These are the various exceptions that the Flow Production Tracking API will raise. .. autoclass:: shotgun_api3.ShotgunError :show-inheritance: @@ -479,7 +479,7 @@ Valid Operators By Data Type Additional Filter Presets ========================= -As of ShotGrid version 7.0 it is possible to also use filter presets. These presets provide a simple +As of Flow Production Tracking version 7.0 it is possible to also use filter presets. These presets provide a simple way to specify powerful query filters that would otherwise be costly and difficult to craft using traditional filters. @@ -651,7 +651,7 @@ date_time :range: Year must be >= 1970 .. note:: - Datetimes are stored as UTC on the server. The ShotGrid API is configured to automatically + Datetimes are stored as UTC on the server. The Flow Production Tracking API is configured to automatically convert between client local time and UTC. This can be overridden. duration @@ -852,15 +852,15 @@ There are three possible states for values returned by an ``image`` field: Event Types *********** -Whenever a user makes a change to any data in ShotGrid, an event log entry record is created, -capturing the value before and after. ShotGrid also logs some additional useful events that help keep -track of various activity on your ShotGrid instance. +Whenever a user makes a change to any data in Flow Production Tracking, an event log entry record is created, +capturing the value before and after. Flow Production Tracking also logs some additional useful events that help keep +track of various activity on your Flow Production Tracking instance. Event-based Triggers ==================== Events are particularlly useful when used in conjunction with a trigger framework like the -`ShotGrid Event Daemon `_. This allows you to +`Flow Production Tracking Event Daemon `_. This allows you to write plug-ins that watch for certain types of events and then run code when they occur. Structure of Event Types @@ -870,11 +870,11 @@ The basic structure of event types is broken into 3 parts: ``Application_EntityType_Action`` -- ``Application``: Is always "Shotgun" for events automatically created by the ShotGrid server. - Other ShotGrid products may use their name in here, for example, Toolkit has its own events +- ``Application``: Is always "Shotgun" for events automatically created by the Flow Production Tracking server. + Other Flow Production Tracking products may use their name in here, for example, Toolkit has its own events that it logs and the application portion is identified by "Toolkit". If you decide to use the EventLogEntry entity to log events for your scripts or tools, you would use your tool name here. -- ``EntityType``: This is the entity type in ShotGrid that was acted upon (eg. Shot, Asset, etc.) +- ``EntityType``: This is the entity type in Flow Production Tracking that was acted upon (eg. Shot, Asset, etc.) - ``Action``: The general action that was taken. (eg. New, Change, Retirement, Revival) @@ -892,14 +892,14 @@ deleted, and revived. They follow this pattern: Additional Event Types ====================== -These are _some_ of the additional event types that are logged by ShotGrid: +These are _some_ of the additional event types that are logged by Flow Production Tracking: - ``Shotgun_Attachment_View``: an Attachment (file) was viewed by a user. - ``Shotgun_Reading_Change``: a threaded entity has been marked read or unread. For example, a Note was read by a user. The readings are unique to the entity<->user connection so when a Note is read by user "joe" it may still be unread by user "jane". -- ``Shotgun_User_Login``: a user logged in to ShotGrid. -- ``Shotgun_User_Logout``: a user logged out of ShotGrid. +- ``Shotgun_User_Login``: a user logged in to Flow Production Tracking. +- ``Shotgun_User_Logout``: a user logged out of Flow Production Tracking. Custom Event Types @@ -907,10 +907,10 @@ Custom Event Types Since ``EventLogEntries`` are entities themselves, you can create them using the API just like any other entity type. As mentioned previously, if you'd like to have your scripts or tools log to -the ShotGrid event log, simply devise a thoughtful naming structure for your event types and +the Flow Production Tracking event log, simply devise a thoughtful naming structure for your event types and create the EventLogEntry as needed following the usual methods for creating entities via the API. -Again, other ShotGrid products like Toolkit use event logs this way. +Again, other Flow Production Tracking products like Toolkit use event logs this way. .. note:: EventLogEntries cannot be updated or deleted (that would defeat the purpose of course). @@ -918,13 +918,13 @@ Again, other ShotGrid products like Toolkit use event logs this way. Performance =========== -Event log database tables can get large very quickly. While ShotGrid does very well with event logs +Event log database tables can get large very quickly. While Flow Production Tracking does very well with event logs that get into the millions of records, there's an inevitable degradation of performance for pages that display them in the web application as well as any API queries for events when they get too big. This volume of events is not the norm, but can be reached if your server expereinces high usage. -This **does not** mean your ShotGrid server performance will suffer in general, just any pages that +This **does not** mean your Flow Production Tracking server performance will suffer in general, just any pages that are specifically displaying EventLogEntries in the web application, or API queries on the event log that are run. We are always looking for ways to improve this in the future. If you have any immediate concerns, please `reach out to our support team `_ @@ -953,7 +953,7 @@ In the case that both this environment variable and the config's ``rpc_attempt_i Localization ************ -The ShotGrid API offers the ability to return localized display names in the current user's language. +The Flow Production Tracking API offers the ability to return localized display names in the current user's language. Requests made from script/API users are localized in the site settings. This functionality is currently supported by the methods ``Shotgun.schema_entity_read``, ``Shotgun.schema_field_read``, and ``Shotgun.schema_read``. diff --git a/setup.py b/setup.py index 93a9de948..a2624aea6 100644 --- a/setup.py +++ b/setup.py @@ -28,9 +28,9 @@ setup( name='shotgun_api3', version='3.4.2', - description='ShotGrid Python API ', + description='Flow Production Tracking Python API ', long_description=readme, - author='ShotGrid Software', + author='Autodesk', author_email='https://www.autodesk.com/support/contact-support', url='https://github.com/shotgunsoftware/python-api', license=license, diff --git a/shotgun_api3/lib/README.md b/shotgun_api3/lib/README.md index e8fc31cd6..1bdec6f78 100644 --- a/shotgun_api3/lib/README.md +++ b/shotgun_api3/lib/README.md @@ -6,7 +6,7 @@ Some third-party modules are bundled with `python-api` inside lib. ### httplib2 -`httplib2` is used to make http connections to the ShotGrid server. We bundle both python2 and python3 compatible versions since httplib2 chose to maintain parallel versions of the module for python 2 and 3 compatibility. +`httplib2` is used to make http connections to the Flow Production Tracking server. We bundle both python2 and python3 compatible versions since httplib2 chose to maintain parallel versions of the module for python 2 and 3 compatibility. The version of `httplib2` bundled should be updated manually, however its version is included in the unused `shotgun_api3/lib/requirements.txt` to allow Github's automated CVE notifications to work. @@ -29,7 +29,7 @@ Six is a Python 2/3 compatibility library. In python-api, it's used to make sim The version of `six` bundled should be updated manually, however its version is included in the unused `shotgun_api3/lib/requirements.txt` to allow Github's automated CVE notifications to work. -## ShotGrid Modules +## Flow Production Tracking Modules ### sgsix @@ -41,15 +41,15 @@ The version of `six` bundled should be updated manually, however its version is ### mockgun -Mockgun is a ShotGrid API mocker. It's a class that has got *most* of the same -methods and parameters that the ShotGrid API has got. Mockgun is essentially a -ShotGrid *emulator* that (for basic operations) looks and feels like ShotGrid. +Mockgun is a Flow Production Tracking API mocker. It's a class that has got *most* of the same +methods and parameters that the Flow Production Tracking API has got. Mockgun is essentially a +Flow Production Tracking *emulator* that (for basic operations) looks and feels like Flow Production Tracking. The primary purpose of Mockgun is to drive unit test rigs where it becomes -too slow, cumbersome or non-practical to connect to a real ShotGrid. Using a +too slow, cumbersome or non-practical to connect to a real Flow Production Tracking. Using a Mockgun for unit tests means that a test can be rerun over and over again from exactly the same database state. This can be hard to do if you connect -to a live ShotGrid instance. +to a live Flow Production Tracking instance. ## Lib `requirements.txt` diff --git a/shotgun_api3/lib/mockgun/mockgun.py b/shotgun_api3/lib/mockgun/mockgun.py index eab05f88d..fbe7ff085 100644 --- a/shotgun_api3/lib/mockgun/mockgun.py +++ b/shotgun_api3/lib/mockgun/mockgun.py @@ -201,7 +201,7 @@ def __init__(self, if schema_path is None or schema_entity_path is None: raise MockgunError("Cannot create Mockgun instance because no schema files have been defined. " "Before creating a Mockgun instance, please call Mockgun.set_schema_paths() " - "in order to specify which ShotGrid schema Mockgun should operate against.") + "in order to specify which Flow Production Tracking schema Mockgun should operate against.") self._schema, self._schema_entity = SchemaFactory.get_schemas(schema_path, schema_entity_path) @@ -504,7 +504,7 @@ def _validate_entity_data(self, entity_type, data): "url": dict}[sg_type] except KeyError: raise ShotgunError( - "Field %s.%s: Handling for ShotGrid type %s is not implemented" % + "Field %s.%s: Handling for Flow Production Tracking type %s is not implemented" % (entity_type, field, sg_type) ) diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index 2ec07823a..4a55145ab 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -224,10 +224,10 @@ def __init__(self, host, meta): except AttributeError: self.version = None if not self.version: - raise ShotgunError("The ShotGrid Server didn't respond with a version number. " + raise ShotgunError("The Flow Production Tracking Server didn't respond with a version number. " "This may be because you are running an older version of " - "ShotGrid against a more recent version of the ShotGrid API. " - "For more information, please contact ShotGrid Support.") + "Flow Production Tracking against a more recent version of the Flow Production Tracking API. " + "For more information, please contact the Autodesk support.") if len(self.version) > 3 and self.version[3] == "Dev": self.is_dev = True @@ -339,7 +339,7 @@ class ClientCapabilities(object): :ivar str platform: The current client platform. Valid values are ``mac``, ``linux``, ``windows``, or ``None`` (if the current platform couldn't be determined). - :ivar str local_path_field: The SG field used for local file paths. This is calculated using + :ivar str local_path_field: The PTR field used for local file paths. This is calculated using the value of ``platform``. Ex. ``local_path_mac``. :ivar str py_version: Simple version of Python executable as a string. Eg. ``2.7``. :ivar str ssl_version: Version of OpenSSL installed. Eg. ``OpenSSL 1.0.2g 1 Mar 2016``. This @@ -3441,8 +3441,8 @@ def _call_rpc(self, method, params, include_auth_params=True, first=False): except ProtocolError as e: e.headers = resp_headers - # We've seen some rare instances of SG returning 502 for issues that - # appear to be caused by something internal to SG. We're going to + # We've seen some rare instances of PTR returning 502 for issues that + # appear to be caused by something internal to PTR. We're going to # allow for limited retries for those specifically. if attempt != max_attempts and e.errcode in [502, 504]: LOG.debug("Got a 502 or 504 response. Waiting and retrying...") @@ -3679,7 +3679,7 @@ def _parse_http_status(self, status): if status[0] >= 300: headers = "HTTP error from server" if status[0] == 503: - errmsg = "ShotGrid is currently down for maintenance or too busy to reply. Please try again later." + errmsg = "Flow Production Tracking is currently down for maintenance or too busy to reply. Please try again later." raise ProtocolError(self.config.server, error_code, errmsg, @@ -3765,12 +3765,12 @@ def _response_errors(self, sg_response): raise UserCredentialsNotAllowedForSSOAuthenticationFault( sg_response.get("message", "Authentication using username/password is not " - "allowed for an SSO-enabled ShotGrid site") + "allowed for an SSO-enabled Flow Production Tracking site") ) elif sg_response.get("error_code") == ERR_OXYG: raise UserCredentialsNotAllowedForOxygenAuthenticationFault( sg_response.get("message", "Authentication using username/password is not " - "allowed for an Autodesk Identity enabled ShotGrid site") + "allowed for an Autodesk Identity enabled Flow Production Tracking site") ) else: # raise general Fault diff --git a/software_credits b/software_credits index aa6e87c5c..819f81ee6 100644 --- a/software_credits +++ b/software_credits @@ -1,4 +1,4 @@ -The ShotGrid Python API uses the following software. Thanks to their creators, license information below. +The Flow Production Tracking Python API uses the following software. Thanks to their creators, license information below. ============================== PYTHON ============================== diff --git a/tests/base.py b/tests/base.py index 5f4b61aa0..01b39bd15 100644 --- a/tests/base.py +++ b/tests/base.py @@ -1,4 +1,4 @@ -"""Base class for ShotGrid API tests.""" +"""Base class for Flow Production Tracking API tests.""" import contextlib import os import random @@ -152,7 +152,7 @@ def _setup_mock(self, s3_status_code_error=503): {"version": [2, 4, 0]}) def _mock_http(self, data, headers=None, status=None): - """Setup a mock response from the SG server. + """Setup a mock response from the PTR server. Only has an affect if the server has been mocked. """ diff --git a/tests/example_config b/tests/example_config index 01501d23d..064eaba6a 100644 --- a/tests/example_config +++ b/tests/example_config @@ -13,7 +13,7 @@ [SERVER_INFO] -# Full url to the ShotGrid server server +# Full url to the Flow Production Tracking server # e.g. https://my-site.shotgrid.autodesk.com # be careful to not end server_url with a "/", or some tests may fail server_url : http://0.0.0.0:3000 @@ -24,7 +24,7 @@ http_proxy : session_uuid : [TEST_DATA] -project_name : SG unittest project +project_name : PTR unittest project human_name : Sg unittest human human_login : sgunittesthuman diff --git a/tests/mockgun/schema.pickle b/tests/mockgun/schema.pickle index 20ef8ee02..affaa8aba 100644 --- a/tests/mockgun/schema.pickle +++ b/tests/mockgun/schema.pickle @@ -86870,7 +86870,7 @@ p42730 I01 sS'value' p42731 -S'File field to contain the uploaded movie file. Used for playback of lower resolution movie media stored in ShotGrid.' +S'File field to contain the uploaded movie file. Used for playback of lower resolution movie media stored in Flow Production Tracking.' p42732 sssS'user' p42733 diff --git a/tests/test_api.py b/tests/test_api.py index be3201fd6..4fdaab038 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1737,7 +1737,7 @@ def test_following(self): '''Test following method''' if not self.sg.server_caps.version or self.sg.server_caps.version < (7, 0, 12): - warnings.warn("Test bypassed because SG server used does not support this feature.", FutureWarning) + warnings.warn("Test bypassed because PTR server used does not support this feature.", FutureWarning) return with self.gen_entity( @@ -2607,7 +2607,7 @@ class TestReadAdditionalFilterPresets(base.LiveTestBase): def test_simple_case(self): if self.sg_version < (7, 0, 0): - warnings.warn("Test bypassed because SG server used does not support this feature.", FutureWarning) + warnings.warn("Test bypassed because PTR server used does not support this feature.", FutureWarning) return filters = [ @@ -2626,7 +2626,7 @@ def test_simple_case(self): def test_find_one(self): if self.sg_version < (7, 0, 0): - warnings.warn("Test bypassed because SG server used does not support this feature.", FutureWarning) + warnings.warn("Test bypassed because PTR server used does not support this feature.", FutureWarning) return filters = [ @@ -2644,7 +2644,7 @@ def test_find_one(self): def test_filter_with_no_name(self): if self.sg_version < (7, 0, 0): - warnings.warn("Test bypassed because SG server used does not support this feature.", FutureWarning) + warnings.warn("Test bypassed because PTR server used does not support this feature.", FutureWarning) return filters = [ @@ -2662,7 +2662,7 @@ def test_filter_with_no_name(self): def test_invalid_filter(self): if self.sg_version < (7, 0, 0): - warnings.warn("Test bypassed because SG server used does not support this feature.", FutureWarning) + warnings.warn("Test bypassed because PTR server used does not support this feature.", FutureWarning) return filters = [ @@ -2680,7 +2680,7 @@ def test_invalid_filter(self): def test_filter_not_iterable(self): if self.sg_version < (7, 0, 0): - warnings.warn("Test bypassed because SG server used does not support this feature.", FutureWarning) + warnings.warn("Test bypassed because PTR server used does not support this feature.", FutureWarning) return filters = [ @@ -2698,7 +2698,7 @@ def test_filter_not_iterable(self): def test_filter_not_list_of_iterable(self): if self.sg_version < (7, 0, 0): - warnings.warn("Test bypassed because SG server used does not support this feature.", FutureWarning) + warnings.warn("Test bypassed because PTR server used does not support this feature.", FutureWarning) return filters = [ @@ -2716,7 +2716,7 @@ def test_filter_not_list_of_iterable(self): def test_multiple_latest_filters(self): if self.sg_version < (7, 0, 0): - warnings.warn("Test bypassed because SG server used does not support this feature.", FutureWarning) + warnings.warn("Test bypassed because PTR server used does not support this feature.", FutureWarning) return filters = [ @@ -2740,7 +2740,7 @@ def test_modify_visibility(self): # If the version of Shotgun is too old, do not run this test. # TODO: Update this with the real version number once the feature is released. if self.sg_version < (8, 5, 0): - warnings.warn("Test bypassed because SG server used does not support this feature.", FutureWarning) + warnings.warn("Test bypassed because PTR server used does not support this feature.", FutureWarning) return field_display_name = "Project Visibility Test" diff --git a/tests/test_client.py b/tests/test_client.py index f7e783b2c..9a6b0f9b8 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -244,7 +244,7 @@ def test_authorization(self): self.assertEqual(expected, headers.get("Authorization")) def test_localization_header_default(self): - """Localization header not passed to server without explicitly setting SG localization config to True""" + """Localization header not passed to server without explicitly setting PTR localization config to True""" self.sg.info() args, _ = self.sg._http_request.call_args @@ -254,7 +254,7 @@ def test_localization_header_default(self): self.assertEqual(None, headers.get("locale")) def test_localization_header_when_localized(self): - """Localization header passed to server when setting SG localization config to True""" + """Localization header passed to server when setting PTR localization config to True""" self.sg.config.localized = True self.sg.info() diff --git a/tests/test_unit.py b/tests/test_unit.py index c8ce4fcc9..84bd35b60 100644 --- a/tests/test_unit.py +++ b/tests/test_unit.py @@ -440,7 +440,7 @@ def _check_url_with_urllib(self, url): """ Given a url it will perform a simple request and return a result. """ - # create a request using the opener generated by the SG API. + # create a request using the opener generated by the PTR API. # The `_build_opener` method internally should use the correct certs. opener = self.sg._build_opener(urllib.request.HTTPHandler) request = urllib.request.Request(url) @@ -460,7 +460,7 @@ def test_found_correct_cert(self): # Call dirname to remove from __init__.py os.path.join(os.path.dirname(api.__file__), "lib", "certifi", "cacert.pem") ) - # Now ensure that the path the SG API has found is correct. + # Now ensure that the path the PTR API has found is correct. self.assertEqual(cert_path, self.certs) self.assertTrue(os.path.isfile(self.certs)) From 527812cb1c06333e493497b9b6debed9a7354891 Mon Sep 17 00:00:00 2001 From: Carlos Villavicencio <123113322+carlos-villavicencio-adsk@users.noreply.github.com> Date: Tue, 26 Mar 2024 07:38:22 -0500 Subject: [PATCH 050/125] Packaging for v3.5.0 (#333) --- HISTORY.rst | 4 ++++ setup.py | 2 +- shotgun_api3/shotgun.py | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 6e3f139fb..24ccbee0e 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -4,6 +4,10 @@ Flow Production Tracking Python API Changelog Here you can see the full list of changes between each Python API release. +v3.5.0 (2024 Mar 26) +=================== +- Rebranding component for Flow Production Tracking + v3.4.2 (2024 Feb 6) =================== - Add support for Python 3.11 diff --git a/setup.py b/setup.py index a2624aea6..cb1596308 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ setup( name='shotgun_api3', - version='3.4.2', + version='3.5.0', description='Flow Production Tracking Python API ', long_description=readme, author='Autodesk', diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index 4a55145ab..03766426f 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -122,7 +122,7 @@ def _is_mimetypes_broken(): # ---------------------------------------------------------------------------- # Version -__version__ = "3.4.2" +__version__ = "3.5.0" # ---------------------------------------------------------------------------- # Errors From 4b8f4275fbd8610251da366b9590222625e13896 Mon Sep 17 00:00:00 2001 From: juanburgosautoglb <156214539+juanburgosautoglb@users.noreply.github.com> Date: Wed, 27 Mar 2024 10:39:55 -0500 Subject: [PATCH 051/125] Fix text issues. (#334) --- docs/reference.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/reference.rst b/docs/reference.rst index c4da96caf..8c9e6af6f 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -26,9 +26,9 @@ Shotgun() .. autoclass:: Shotgun :show-inheritance: -******************************** -Flow Production Tracking Methods -******************************** +*************** +Shotgun Methods +*************** The majority of functionality is contained within the :class:`~shotgun_api3.Shotgun` class. The documentation for all of the methods you'll need in your scripts lives in here. @@ -91,7 +91,7 @@ The documentation for all of the methods you'll need in your scripts lives in he Shotgun.followers Shotgun.following -.. rubric:: Working with the Flow Production Tracking Schema and Preferences +.. rubric:: Working with the Shotgun Schema and Preferences .. autosummary:: :nosignatures: @@ -177,10 +177,10 @@ Methods that relate to the activity stream and following of entities in Flow Pro .. automethod:: Shotgun.followers .. automethod:: Shotgun.following -Working with the Flow Production Tracking Schema -================================================ +Working with the Shotgun Schema +=============================== -Methods allow you to introspect and modify the Flow Production Tracking schema. +Methods allow you to introspect and modify the Shotgun schema. .. automethod:: Shotgun.schema_entity_read .. automethod:: Shotgun.schema_field_read From 0079944df994ff12d29ec084d72fdc9a07908bee Mon Sep 17 00:00:00 2001 From: Barbara Laigneau <38566472+barbara-darkshot@users.noreply.github.com> Date: Wed, 3 Apr 2024 14:31:54 +0200 Subject: [PATCH 052/125] add some missing api public method needed by the Jira Bridge tests and fix some wrong data type (#332) --- shotgun_api3/lib/mockgun/mockgun.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/shotgun_api3/lib/mockgun/mockgun.py b/shotgun_api3/lib/mockgun/mockgun.py index fbe7ff085..72665bba9 100644 --- a/shotgun_api3/lib/mockgun/mockgun.py +++ b/shotgun_api3/lib/mockgun/mockgun.py @@ -425,6 +425,12 @@ def upload(self, entity_type, entity_id, path, field_name=None, display_name=Non def upload_thumbnail(self, entity_type, entity_id, path, **kwargs): pass + def add_user_agent(self, agent): + pass + + def set_session_uuid(self, session_uuid): + pass + ################################################################################################### # internal methods and members @@ -497,8 +503,9 @@ def _validate_entity_data(self, entity_type, data): "text": six.string_types, "serializable": dict, "entity_type": six.string_types, - "date": datetime.date, + "date": six.string_types, "date_time": datetime.datetime, + "duration": int, "list": six.string_types, "status_list": six.string_types, "url": dict}[sg_type] From a199f04f23ed4e8d0753bf9d95af95c5a3b6a42d Mon Sep 17 00:00:00 2001 From: Barbara Laigneau <38566472+barbara-darkshot@users.noreply.github.com> Date: Wed, 3 Apr 2024 15:30:55 +0200 Subject: [PATCH 053/125] Packaging for v3.5.1 (#335) --- HISTORY.rst | 6 ++++++ setup.py | 2 +- shotgun_api3/shotgun.py | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 24ccbee0e..af065fe8d 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -4,6 +4,12 @@ Flow Production Tracking Python API Changelog Here you can see the full list of changes between each Python API release. +v3.5.1 (2024 Apr 3) +=================== +- Documentation: Revert to Shotgun in the API Reference headers to keep consistency with the API methods +- Mockgun: fix entity data type +- Mockgun: add support for ``add_user_agent`` and ``set_session_uuid`` methods + v3.5.0 (2024 Mar 26) =================== - Rebranding component for Flow Production Tracking diff --git a/setup.py b/setup.py index cb1596308..25182874f 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ setup( name='shotgun_api3', - version='3.5.0', + version='3.5.1', description='Flow Production Tracking Python API ', long_description=readme, author='Autodesk', diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index 03766426f..c35d7644d 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -122,7 +122,7 @@ def _is_mimetypes_broken(): # ---------------------------------------------------------------------------- # Version -__version__ = "3.5.0" +__version__ = "3.5.1" # ---------------------------------------------------------------------------- # Errors From 2641f7b0102d985009f6f93940cf6528a75e8bee Mon Sep 17 00:00:00 2001 From: Eduardo Chauca <166560435+eduardoChaucaGallegos@users.noreply.github.com> Date: Fri, 26 Apr 2024 11:49:04 -0500 Subject: [PATCH 054/125] certifi version changed to 2024.2.2 (#338) --- shotgun_api3/lib/certifi/__init__.py | 2 +- shotgun_api3/lib/certifi/cacert.pem | 321 +++++++++++++++++++++------ shotgun_api3/lib/certifi/core.py | 6 + shotgun_api3/lib/requirements.txt | 2 +- 4 files changed, 258 insertions(+), 73 deletions(-) diff --git a/shotgun_api3/lib/certifi/__init__.py b/shotgun_api3/lib/certifi/__init__.py index 8ce89cef7..1c91f3ec9 100644 --- a/shotgun_api3/lib/certifi/__init__.py +++ b/shotgun_api3/lib/certifi/__init__.py @@ -1,4 +1,4 @@ from .core import contents, where __all__ = ["contents", "where"] -__version__ = "2023.07.22" +__version__ = "2024.02.02" diff --git a/shotgun_api3/lib/certifi/cacert.pem b/shotgun_api3/lib/certifi/cacert.pem index 02123695d..fac3c3190 100644 --- a/shotgun_api3/lib/certifi/cacert.pem +++ b/shotgun_api3/lib/certifi/cacert.pem @@ -245,34 +245,6 @@ mJlglFwjz1onl14LBQaTNx47aTbrqZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK 4SVhM7JZG+Ju1zdXtg2pEto= -----END CERTIFICATE----- -# Issuer: O=SECOM Trust.net OU=Security Communication RootCA1 -# Subject: O=SECOM Trust.net OU=Security Communication RootCA1 -# Label: "Security Communication Root CA" -# Serial: 0 -# MD5 Fingerprint: f1:bc:63:6a:54:e0:b5:27:f5:cd:e7:1a:e3:4d:6e:4a -# SHA1 Fingerprint: 36:b1:2b:49:f9:81:9e:d7:4c:9e:bc:38:0f:c6:56:8f:5d:ac:b2:f7 -# SHA256 Fingerprint: e7:5e:72:ed:9f:56:0e:ec:6e:b4:80:00:73:a4:3f:c3:ad:19:19:5a:39:22:82:01:78:95:97:4a:99:02:6b:6c ------BEGIN CERTIFICATE----- -MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEY -MBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21t -dW5pY2F0aW9uIFJvb3RDQTEwHhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5 -WjBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYD -VQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEwggEiMA0GCSqGSIb3 -DQEBAQUAA4IBDwAwggEKAoIBAQCzs/5/022x7xZ8V6UMbXaKL0u/ZPtM7orw8yl8 -9f/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzGjGdnSj74cbAZJ6kJ -DKaVv0uMDPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1aeV+7AwFb9 -Ms+k2Y7CI9eNqPPYJayX5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6+HGhWZq/N -QV3Is00qVUarH9oe4kA92819uZKAnDfdDJZkndwi92SL32HeFZRSFaB9UslLqCHJ -xrHty8OVYNEP8Ktw+N/LTX7s1vqr2b1/VPKl6Xn62dZ2JChzAgMBAAGjPzA9MB0G -A1UdDgQWBBSgc0mZaNyFW2XjmygvV5+9M7wHSDALBgNVHQ8EBAMCAQYwDwYDVR0T -AQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vG -kl3g0dNq/vu+m22/xwVtWSDEHPC32oRYAmP6SBbvT6UL90qY8j+eG61Ha2POCEfr -Uj94nK9NrvjVT8+amCoQQTlSxN3Zmw7vkwGusi7KaEIkQmywszo+zenaSMQVy+n5 -Bw+SUEmK3TGXX8npN6o7WWWXlDLJs58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJU -JRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ6rBK+1YWc26sTfcioU+tHXot -RSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAiFL39vmwLAw== ------END CERTIFICATE----- - # Issuer: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com # Subject: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com # Label: "XRamp Global CA Root" @@ -881,49 +853,6 @@ Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH WD9f -----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: 6047274297262753887 -# MD5 Fingerprint: 73:3a:74:7a:ec:bb:a3:96:a6:c2:e4:e2:c8:9b:c0:c3 -# SHA1 Fingerprint: ae:c5:fb:3f:c8:e1:bf:c4:e5:4f:03:07:5a:9a:e8:00:b7:f7:b6:fa -# SHA256 Fingerprint: 04:04:80:28:bf:1f:28:64:d4:8f:9a:d4:d8:32:94:36:6a:82:88:56:55:3f:3b:14:30:3f:90:14:7f:5d:40:ef ------BEGIN CERTIFICATE----- -MIIGFDCCA/ygAwIBAgIIU+w77vuySF8wDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UE -BhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h -cHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0wOTA1MjAwODM4MTVaFw0zMDEy -MzEwODM4MTVaMFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUg -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+JIwuwIDAQABo4HvMIHsMBIGA1UdEwEB/wQIMAYBAf8CAQEwDgYD -VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRlzeurNR4APn7VdMActHNHDhpkLzCBpgYD -VR0gBIGeMIGbMIGYBgRVHSAAMIGPMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmZp -cm1hcHJvZmVzaW9uYWwuY29tL2NwczBcBggrBgEFBQcCAjBQHk4AUABhAHMAZQBv -ACAAZABlACAAbABhACAAQgBvAG4AYQBuAG8AdgBhACAANAA3ACAAQgBhAHIAYwBl -AGwAbwBuAGEAIAAwADgAMAAxADcwDQYJKoZIhvcNAQEFBQADggIBABd9oPm03cXF -661LJLWhAqvdpYhKsg9VSytXjDvlMd3+xDLx51tkljYyGOylMnfX40S2wBEqgLk9 -am58m9Ot/MPWo+ZkKXzR4Tgegiv/J2Wv+xYVxC5xhOW1//qkR71kMrv2JYSiJ0L1 -ILDCExARzRAVukKQKtJE4ZYm6zFIEv0q2skGz3QeqUvVhyj5eTSSPi5E6PaPT481 -PyWzOdxjKpBrIF/EUhJOlywqrJ2X3kjyo2bbwtKDlaZmp54lD+kLM5FlClrD2VQS -3a/DTg4fJl4N3LON7NWBcN7STyQF82xO9UxJZo3R/9ILJUFI/lGExkKvgATP0H5k -SeTy36LssUzAKh3ntLFlosS88Zj0qnAHY7S42jtM+kAiMFsRpvAFDsYCA0irhpuF -3dvd6qJ2gHN99ZwExEWN57kci57q13XRcrHedUTnQn3iV2t93Jm8PYMo6oCTjcVM -ZcFwgbg4/EMxsvYDNEeyrPsiBsse3RdHHF9mudMaotoRsaS8I8nkvof/uZS2+F0g -StRf571oe2XyFR7SOqkt6dhrJKyXWERHrVkY8SFlcN7ONGCoQPHzPKTDKCOM/icz -Q0CgFzzr6juwcqajuUpLXhZI9LK8yIySxZ2frHI2vDSANGupi5LAuBft7HZT9SQB -jLMi6Et8Vcad+qMUu2WFbm5PEn4KPJ2V ------END CERTIFICATE----- - # Issuer: CN=Izenpe.com O=IZENPE S.A. # Subject: CN=Izenpe.com O=IZENPE S.A. # Label: "Izenpe.com" @@ -4633,3 +4562,253 @@ 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----- diff --git a/shotgun_api3/lib/certifi/core.py b/shotgun_api3/lib/certifi/core.py index de028981b..91f538bb1 100644 --- a/shotgun_api3/lib/certifi/core.py +++ b/shotgun_api3/lib/certifi/core.py @@ -5,6 +5,10 @@ This module returns the installation location of cacert.pem or its contents. """ import sys +import atexit + +def exit_cacert_ctx() -> None: + _CACERT_CTX.__exit__(None, None, None) # type: ignore[union-attr] if sys.version_info >= (3, 11): @@ -35,6 +39,7 @@ def where() -> str: # we will also store that at the global level as well. _CACERT_CTX = as_file(files("certifi").joinpath("cacert.pem")) _CACERT_PATH = str(_CACERT_CTX.__enter__()) + atexit.register(exit_cacert_ctx) return _CACERT_PATH @@ -70,6 +75,7 @@ def where() -> str: # we will also store that at the global level as well. _CACERT_CTX = get_path("certifi", "cacert.pem") _CACERT_PATH = str(_CACERT_CTX.__enter__()) + atexit.register(exit_cacert_ctx) return _CACERT_PATH diff --git a/shotgun_api3/lib/requirements.txt b/shotgun_api3/lib/requirements.txt index cf47abb45..6278cb3f1 100644 --- a/shotgun_api3/lib/requirements.txt +++ b/shotgun_api3/lib/requirements.txt @@ -30,5 +30,5 @@ # released for our dependencies. httplib2==0.22.0 six==1.13.0 -certifi==2023.7.22 +certifi==2024.2.2 pyparsing==2.4.7 \ No newline at end of file From 725646c181e2b0bcd5a2ebde19f1e61d81e617c4 Mon Sep 17 00:00:00 2001 From: Eduardo Chauca <166560435+eduardoChaucaGallegos@users.noreply.github.com> Date: Tue, 30 Apr 2024 12:20:39 -0500 Subject: [PATCH 055/125] SG-34983 Documentation title changed from API3 to API (#339) * documentation changed from Flow Production Tracking Python API3 to Flow Production Tracking Python API --- docs/cookbook/usage_tips.rst | 2 +- docs/index.rst | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/cookbook/usage_tips.rst b/docs/cookbook/usage_tips.rst index 620a96426..c2aa362b2 100644 --- a/docs/cookbook/usage_tips.rst +++ b/docs/cookbook/usage_tips.rst @@ -2,7 +2,7 @@ API Usage Tips ############## -Below is a list of helpful tips when using the Flow Production Tracking API. We have tried to make the API very +Below is a list of helpful tips when using the Flow Production Tracking API3. We have tried to make the API very simple to use with predictable results while remaining a powerful tool to integrate with your pipeline. However, there's always a couple of things that crop up that our users might not be aware of. Those are the types of things you'll find below. We'll be adding to this document over diff --git a/docs/index.rst b/docs/index.rst index 5eef5a61c..c3ed66ce0 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,21 +1,21 @@ -#################################### -Flow Production Tracking Python API3 -#################################### +########################################### +Flow Production Tracking Python API library +########################################### Release |version|. (:ref:`Installation `) .. image:: https://img.shields.io/badge/shotgun-api-blue.svg -Flow Production Tracking provides a simple Python-based API for accessing Flow Production Tracking and integrating with other tools. -The Flow Production Tracking API allows users to integrate their tools with Flow Production Tracking very easily. Using this simple -but powerful python module , you can quickly get your scripts integrated with Flow Production Tracking's CRUD-based +Flow Production Tracking (FPTR) provides a simple Python-based API for accessing FPTR and integrating with other tools. +The Flow Production Tracking API3, also known as "Python API", allows users to integrate their tools with Flow Production Tracking very easily. Using this simple +but powerful python module, you can quickly get your scripts integrated with Flow Production Tracking's CRUD-based API. Because the needs of every studio can prove to be very different, we don't include a lot of "automation" or "smarts" in our API. We have kept it pretty low-level and leave most of those decisions to you. The API is powerful enough you can write your own "smarts" in a wrapper on top -of the Flow Production Tracking API. +of the of the FPTR API3. .. _pythonoverviewvideo: From 2c50a2c640d8283808bf80108ff09aafa8f6a7b3 Mon Sep 17 00:00:00 2001 From: Carlos Villavicencio <123113322+carlos-villavicencio-adsk@users.noreply.github.com> Date: Wed, 8 May 2024 14:43:15 -0500 Subject: [PATCH 056/125] Packaging for v3.6.0 (#340) * Packaging for v3.6.0 * Add Python version check --- HISTORY.rst | 6 ++++++ setup.py | 15 +++++++++++++-- shotgun_api3/shotgun.py | 10 +++++++++- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index af065fe8d..ca83eae40 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -4,6 +4,12 @@ Flow Production Tracking Python API Changelog Here you can see the full list of changes between each Python API release. +v3.6.0 (2024 May 1) +=================== +- Drop support for Python 2.7 +- certifi version changed to 2024.2.2 +- Documentation update + v3.5.1 (2024 Apr 3) =================== - Documentation: Revert to Shotgun in the API Reference headers to keep consistency with the API methods diff --git a/setup.py b/setup.py index 25182874f..12653f21e 100644 --- a/setup.py +++ b/setup.py @@ -27,8 +27,8 @@ setup( name='shotgun_api3', - version='3.5.1', - description='Flow Production Tracking Python API ', + version='3.6.0', + description='Flow Production Tracking Python API', long_description=readme, author='Autodesk', author_email='https://www.autodesk.com/support/contact-support', @@ -39,4 +39,15 @@ include_package_data=True, package_data={'': ['cacerts.txt', 'cacert.pem']}, zip_safe=False, + python_requires=">=3.7.0", + classifiers=[ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Programming Language :: Python", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Operating System :: OS Independent", + ], ) diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index c35d7644d..3adacb92f 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -122,7 +122,7 @@ def _is_mimetypes_broken(): # ---------------------------------------------------------------------------- # Version -__version__ = "3.5.1" +__version__ = "3.6.0" # ---------------------------------------------------------------------------- # Errors @@ -213,6 +213,7 @@ def __init__(self, host, meta): :ivar bool is_dev: ``True`` if server is running a development version of the Shotgun codebase. """ + self._ensure_python_version_supported() # Server host name self.host = host self.server_info = meta @@ -237,6 +238,13 @@ def __init__(self, host, meta): self.version = tuple(self.version[:3]) self._ensure_json_supported() + def _ensure_python_version_supported(self): + """ + Checks the if current Python version is supported. + """ + if sys.version_info < (3, 7): + raise ShotgunError("This module requires Python version 3.7 or higher.") + def _ensure_support(self, feature, raise_hell=True): """ Checks the server version supports a given feature, raises an exception if it does not. From 73193e3f91ee0791401ca876a5a095285d96df5f Mon Sep 17 00:00:00 2001 From: slingshotvfx <146885925+slingshotvfx@users.noreply.github.com> Date: Fri, 17 May 2024 08:55:55 -0700 Subject: [PATCH 057/125] add multi_entity_update_modes support to mockgun (#330) --- shotgun_api3/lib/mockgun/mockgun.py | 28 ++++++-- tests/test_mockgun.py | 100 ++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+), 5 deletions(-) diff --git a/shotgun_api3/lib/mockgun/mockgun.py b/shotgun_api3/lib/mockgun/mockgun.py index 72665bba9..4bf1e4bf4 100644 --- a/shotgun_api3/lib/mockgun/mockgun.py +++ b/shotgun_api3/lib/mockgun/mockgun.py @@ -335,7 +335,12 @@ def batch(self, requests): results.append(self.create(request["entity_type"], request["data"])) elif request["request_type"] == "update": # note: Shotgun.update returns a list of a single item - results.append(self.update(request["entity_type"], request["entity_id"], request["data"])[0]) + results.append( + self.update(request["entity_type"], + request["entity_id"], + request["data"], + request.get("multi_entity_update_modes"))[0] + ) elif request["request_type"] == "delete": results.append(self.delete(request["entity_type"], request["entity_id"])) else: @@ -387,13 +392,13 @@ def create(self, entity_type, data, return_fields=None): return result - def update(self, entity_type, entity_id, data): + def update(self, entity_type, entity_id, data, multi_entity_update_modes=None): self._validate_entity_type(entity_type) self._validate_entity_data(entity_type, data) self._validate_entity_exists(entity_type, entity_id) row = self._db[entity_type][entity_id] - self._update_row(entity_type, row, data) + self._update_row(entity_type, row, data, multi_entity_update_modes) return [dict((field, item) for field, item in row.items() if field in data or field in ("type", "id"))] @@ -818,13 +823,26 @@ def _row_matches_filters(self, entity_type, row, filters, filter_operator, retir else: raise ShotgunError("%s is not a valid filter operator" % filter_operator) - def _update_row(self, entity_type, row, data): + def _update_row(self, entity_type, row, data, multi_entity_update_modes=None): for field in data: field_type = self._get_field_type(entity_type, field) if field_type == "entity" and data[field]: row[field] = {"type": data[field]["type"], "id": data[field]["id"]} elif field_type == "multi_entity": - row[field] = [{"type": item["type"], "id": item["id"]} for item in data[field]] + update_mode = multi_entity_update_modes.get(field, "set") if multi_entity_update_modes else "set" + + if update_mode == "add": + row[field] += [{"type": item["type"], "id": item["id"]} for item in data[field]] + elif update_mode == "remove": + row[field] = [ + item + for item in row[field] + for new_item in data[field] + if item["id"] != new_item["id"] + or item["type"] != new_item["type"] + ] + elif update_mode == "set": + row[field] = [{"type": item["type"], "id": item["id"]} for item in data[field]] else: row[field] = data[field] diff --git a/tests/test_mockgun.py b/tests/test_mockgun.py index 08976d2aa..84e5cb2e7 100644 --- a/tests/test_mockgun.py +++ b/tests/test_mockgun.py @@ -270,6 +270,106 @@ def test_find_with_none(self): for item in items: self.assertTrue(len(item["users"]) > 0) + +class TestMultiEntityFieldUpdate(unittest.TestCase): + """ + Ensures multi entity field update modes work. + """ + + def setUp(self): + """ + Creates test data. + """ + + self._mockgun = Mockgun("https://test.shotgunstudio.com", login="user", password="1234") + + # Create two versions to assign to the shot. + self._version1 = self._mockgun.create("Version", {"code": "version1"}) + self._version2 = self._mockgun.create("Version", {"code": "version2"}) + self._version3 = self._mockgun.create("Version", {"code": "version3"}) + + # remove 'code' field for later comparisons + del self._version1["code"] + del self._version2["code"] + del self._version3["code"] + + # Create playlists + self._add_playlist = self._mockgun.create( + "Playlist", + {"code": "playlist1", "versions": [self._version1, self._version2]} + ) + self._remove_playlist = self._mockgun.create( + "Playlist", + {"code": "playlist1", "versions": [self._version1, self._version2, self._version3]} + ) + self._set_playlist = self._mockgun.create( + "Playlist", + {"code": "playlist1", "versions": [self._version1, self._version2]} + ) + + def test_update_add(self): + """ + Ensures that "add" multi_entity_update_mode works. + """ + self._mockgun.update( + "Playlist", self._add_playlist["id"], {"versions": [self._version3]}, + multi_entity_update_modes={"versions": "add"} + ) + + playlist = self._mockgun.find_one( + "Playlist", [["id", "is", self._add_playlist["id"]]], ["versions"] + ) + self.assertEqual( + playlist["versions"], [self._version1, self._version2, self._version3] + ) + + def test_update_remove(self): + """ + Ensures that "remove" multi_entity_update_mode works. + """ + self._mockgun.update( + "Playlist", self._remove_playlist["id"], {"versions": [self._version2]}, + multi_entity_update_modes={"versions": "remove"} + ) + + playlist = self._mockgun.find_one( + "Playlist", [["id", "is", self._remove_playlist["id"]]], ["versions"] + ) + self.assertEqual(playlist["versions"], [self._version1, self._version3]) + + def test_update_set(self): + """ + Ensures that "set" multi_entity_update_mode works. + """ + self._mockgun.update( + "Playlist", + self._set_playlist["id"], + {"versions": [self._version2, self._version3]}, + multi_entity_update_modes={"versions": "set"} + ) + + playlist = self._mockgun.find_one( + "Playlist", [["id", "is", self._set_playlist["id"]]], ["versions"] + ) + self.assertEqual(playlist["versions"], [self._version2, self._version3]) + + def test_batch_update(self): + self._mockgun.batch( + [ + { + "request_type": "update", + "entity_type": "Playlist", + "entity_id": self._set_playlist["id"], + "data": {"versions": [self._version1, self._version2]}, + "multi_entity_update_modes": {"versions": "set"} + } + ] + ) + playlist = self._mockgun.find_one( + "Playlist", [["id", "is", self._set_playlist["id"]]], ["versions"] + ) + self.assertEqual(playlist["versions"], [self._version1, self._version2]) + class TestFilterOperator(unittest.TestCase): """ From b61ed0924e891c1ddd2052dcda374f833df432bc Mon Sep 17 00:00:00 2001 From: Carlos Villavicencio <123113322+carlos-villavicencio-adsk@users.noreply.github.com> Date: Thu, 6 Jun 2024 07:28:25 -0500 Subject: [PATCH 058/125] SG-35165 Retry on URLError (#342) * Retry on URLError * Fix test * Reduce attempt * Use constant --- shotgun_api3/shotgun.py | 61 ++++++++++++++++++------------- tests/base.py | 2 ++ tests/test_client.py | 80 +++++++++++++++++++++++++++++++++++------ 3 files changed, 108 insertions(+), 35 deletions(-) diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index 3adacb92f..84322f028 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -500,6 +500,8 @@ class Shotgun(object): r"(\D?([01]\d|2[0-3])\D?([0-5]\d)\D?([0-5]\d)?\D?(\d{3})?)?$") _MULTIPART_UPLOAD_CHUNK_SIZE = 20000000 + MAX_ATTEMPTS = 3 # Retries on failure + BACKOFF = 0.75 # Seconds to wait before retry, times the attempt number def __init__(self, base_url, @@ -3431,10 +3433,7 @@ def _call_rpc(self, method, params, include_auth_params=True, first=False): req_headers["locale"] = "auto" attempt = 1 - max_attempts = 4 # Three retries on failure - backoff = 0.75 # Seconds to wait before retry, times the attempt number - - while attempt <= max_attempts: + while attempt <= self.MAX_ATTEMPTS: http_status, resp_headers, body = self._make_call( "POST", self.config.api_path, @@ -3452,9 +3451,9 @@ def _call_rpc(self, method, params, include_auth_params=True, first=False): # We've seen some rare instances of PTR returning 502 for issues that # appear to be caused by something internal to PTR. We're going to # allow for limited retries for those specifically. - if attempt != max_attempts and e.errcode in [502, 504]: + if attempt != self.MAX_ATTEMPTS and e.errcode in [502, 504]: LOG.debug("Got a 502 or 504 response. Waiting and retrying...") - time.sleep(float(attempt) * backoff) + time.sleep(float(attempt) * self.BACKOFF) attempt += 1 continue elif e.errcode == 403: @@ -4143,28 +4142,31 @@ def _upload_data_to_storage(self, data, content_type, size, storage_url): request.get_method = lambda: "PUT" attempt = 1 - max_attempts = 4 # Three retries on failure - backoff = 0.75 # Seconds to wait before retry, times the attempt number - - while attempt <= max_attempts: + while attempt <= self.MAX_ATTEMPTS: try: result = self._make_upload_request(request, opener) LOG.debug("Completed request to %s" % request.get_method()) except urllib.error.HTTPError as e: - if attempt != max_attempts and e.code in [500, 503]: + if attempt != self.MAX_ATTEMPTS and e.code in [500, 503]: LOG.debug("Got a %s response. Waiting and retrying..." % e.code) - time.sleep(float(attempt) * backoff) + time.sleep(float(attempt) * self.BACKOFF) attempt += 1 continue elif e.code in [500, 503]: raise ShotgunError("Got a %s response when uploading to %s: %s" % (e.code, storage_url, e)) else: raise ShotgunError("Unanticipated error occurred uploading to %s: %s" % (storage_url, e)) - + except urllib.error.URLError as e: + LOG.debug("Got a '%s' response. Waiting and retrying..." % e) + time.sleep(float(attempt) * self.BACKOFF) + attempt += 1 + continue else: break + else: + raise ShotgunError("Max attemps limit reached.") etag = result.info()["Etag"] LOG.debug("Part upload completed successfully.") @@ -4250,19 +4252,28 @@ def _send_form(self, url, params): opener = self._build_opener(FormPostHandler) - # Perform the request - try: - resp = opener.open(url, params) - result = resp.read() - # response headers are in str(resp.info()).splitlines() - except urllib.error.HTTPError as e: - if e.code == 500: - raise ShotgunError("Server encountered an internal error. " - "\n%s\n(%s)\n%s\n\n" % (url, self._sanitize_auth_params(params), e)) - else: - raise ShotgunError("Unanticipated error occurred %s" % (e)) + attempt = 1 + while attempt <= self.MAX_ATTEMPTS: + # Perform the request + try: + resp = opener.open(url, params) + result = resp.read() + # response headers are in str(resp.info()).splitlines() + except urllib.error.URLError as e: + LOG.debug("Got a %s response. Waiting and retrying..." % e) + time.sleep(float(attempt) * self.BACKOFF) + attempt += 1 + continue + except urllib.error.HTTPError as e: + if e.code == 500: + raise ShotgunError("Server encountered an internal error. " + "\n%s\n(%s)\n%s\n\n" % (url, self._sanitize_auth_params(params), e)) + else: + raise ShotgunError("Unanticipated error occurred %s" % (e)) - return six.ensure_text(result) + return six.ensure_text(result) + else: + raise ShotgunError("Max attemps limit reached.") class CACertsHTTPSConnection(http_client.HTTPConnection): diff --git a/tests/base.py b/tests/base.py index 01b39bd15..28547cbeb 100644 --- a/tests/base.py +++ b/tests/base.py @@ -150,6 +150,8 @@ def _setup_mock(self, s3_status_code_error=503): # create the server caps directly to say we have the correct version self.sg._server_caps = ServerCapabilities(self.sg.config.server, {"version": [2, 4, 0]}) + # prevent waiting for backoff + self.sg.BACKOFF = 0 def _mock_http(self, data, headers=None, status=None): """Setup a mock response from the PTR server. diff --git a/tests/test_client.py b/tests/test_client.py index 9a6b0f9b8..6275e4d28 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -438,9 +438,9 @@ def test_call_rpc(self): self._mock_http(d, status=(502, "bad gateway")) self.assertRaises(api.ProtocolError, self.sg._call_rpc, "list", a) self.assertEqual( - 4, + self.sg.MAX_ATTEMPTS, self.sg._http_request.call_count, - "Call is repeated up to 3 times", + f"Call is repeated up to {self.sg.MAX_ATTEMPTS} times", ) # 504 @@ -449,9 +449,9 @@ def test_call_rpc(self): self._mock_http(d, status=(504, "gateway timeout")) self.assertRaises(api.ProtocolError, self.sg._call_rpc, "list", a) self.assertEqual( - 4, + self.sg.MAX_ATTEMPTS, self.sg._http_request.call_count, - "Call is repeated up to 3 times", + f"Call is repeated up to {self.sg.MAX_ATTEMPTS} times", ) def test_upload_s3_503(self): @@ -462,7 +462,6 @@ def test_upload_s3_503(self): storage_url = "http://foo.com/" path = os.path.abspath(os.path.expanduser( os.path.join(this_dir, "sg_logo.jpg"))) - max_attempts = 4 # Max retries to S3 server attempts # Expected HTTPError exception error message expected = "The server is currently down or to busy to reply." \ "Please try again later." @@ -474,8 +473,8 @@ def test_upload_s3_503(self): self.sg._upload_file_to_storage(path, storage_url) # Test the max retries attempt self.assertTrue( - max_attempts == self.sg._make_upload_request.call_count, - "Call is repeated up to 3 times") + self.sg.MAX_ATTEMPTS == self.sg._make_upload_request.call_count, + f"Call is repeated up to {self.sg.MAX_ATTEMPTS} times") def test_upload_s3_500(self): """ @@ -486,7 +485,6 @@ def test_upload_s3_500(self): storage_url = "http://foo.com/" path = os.path.abspath(os.path.expanduser( os.path.join(this_dir, "sg_logo.jpg"))) - max_attempts = 4 # Max retries to S3 server attempts # Expected HTTPError exception error message expected = "The server is currently down or to busy to reply." \ "Please try again later." @@ -498,8 +496,70 @@ def test_upload_s3_500(self): self.sg._upload_file_to_storage(path, storage_url) # Test the max retries attempt self.assertTrue( - max_attempts == self.sg._make_upload_request.call_count, - "Call is repeated up to 3 times") + self.sg.MAX_ATTEMPTS == self.sg._make_upload_request.call_count, + f"Call is repeated up to {self.sg.MAX_ATTEMPTS} times") + + def test_upload_s3_urlerror__get_attachment_upload_info(self): + """ + Test URLError response is retried when invoking _send_form + """ + mock_opener = mock.Mock() + mock_opener.return_value.open.side_effect = urllib.error.URLError( + "[WinError 10054] An existing connection was forcibly closed by the remote host" + ) + self.sg._build_opener = mock_opener + this_dir, _ = os.path.split(__file__) + path = os.path.abspath( + os.path.expanduser(os.path.join(this_dir, "sg_logo.jpg")) + ) + + with self.assertRaises(api.ShotgunError) as cm: + self.sg._get_attachment_upload_info(False, path, False) + + # Test the max retries attempt + self.assertEqual( + self.sg.MAX_ATTEMPTS, + mock_opener.return_value.open.call_count, + f"Call is repeated up to {self.sg.MAX_ATTEMPTS} times" + ) + + # Test the exception message + the_exception = cm.exception + self.assertEqual(str(the_exception), "Max attemps limit reached.") + + def test_upload_s3_urlerror__upload_to_storage(self): + """ + Test URLError response is retried when uploading to S3. + """ + self.sg._make_upload_request = mock.Mock( + spec=api.Shotgun._make_upload_request, + side_effect=urllib.error.URLError( + "[Errno 104] Connection reset by peer", + ), + ) + + this_dir, _ = os.path.split(__file__) + storage_url = "http://foo.com/" + path = os.path.abspath( + os.path.expanduser(os.path.join(this_dir, "sg_logo.jpg")) + ) + + # Test the Internal function that is used to upload each + # data part in the context of multi-part uploads to S3, we + # simulate the HTTPError exception raised with 503 status errors + with self.assertRaises(api.ShotgunError) as cm: + self.sg._upload_file_to_storage(path, storage_url) + + # Test the max retries attempt + self.assertEqual( + self.sg.MAX_ATTEMPTS, + self.sg._make_upload_request.call_count, + f"Call is repeated up to {self.sg.MAX_ATTEMPTS} times" + ) + + # Test the exception message + the_exception = cm.exception + self.assertEqual(str(the_exception), "Max attemps limit reached.") def test_transform_data(self): """Outbound data is transformed""" From 2e1cd50d7adf7dbe7a7458619b4b657bd1d96527 Mon Sep 17 00:00:00 2001 From: Eduardo Chauca <166560435+eduardoChaucaGallegos@users.noreply.github.com> Date: Thu, 6 Jun 2024 07:58:36 -0500 Subject: [PATCH 059/125] =?UTF-8?q?SG-34551=20validation=20for=20python3,?= =?UTF-8?q?=20so=20display=5Fname=20now=20is=20only=20decoded=20=E2=80=A6?= =?UTF-8?q?=20(#337)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * SG-34551 Old code for python2 was deleted for upload_sg, so the fix was covered with this deletion --- shotgun_api3/shotgun.py | 22 +----- .../No\303\253l\343\201\224.jpg" | Bin tests/test_api.py | 63 +++++++++++++++++- 3 files changed, 64 insertions(+), 21 deletions(-) rename "tests/No\303\253l.jpg" => "tests/No\303\253l\343\201\224.jpg" (100%) diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index 84322f028..5e4595299 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -2569,30 +2569,12 @@ def _upload_to_sg(self, entity_type, entity_id, path, field_name, display_name, params.update(self._auth_params()) - # If we ended up with a unicode string path, we need to encode it - # as a utf-8 string. If we don't, there's a chance that there will - # will be an attempt later on to encode it as an ascii string, and - # that will fail ungracefully if the path contains any non-ascii - # characters. - # - # On Windows, if the path contains non-ascii characters, the calls - # to open later in this method will fail to find the file if given - # a non-ascii-encoded string path. In that case, we're going to have - # to call open on the unicode path, but we'll use the encoded string - # for everything else. - path_to_open = path - if isinstance(path, six.text_type): - path = path.encode("utf-8") - if sys.platform != "win32": - path_to_open = path - if is_thumbnail: url = urllib.parse.urlunparse((self.config.scheme, self.config.server, "/upload/publish_thumbnail", None, None, None)) - params["thumb_image"] = open(path_to_open, "rb") + params["thumb_image"] = open(path, "rb") if field_name == "filmstrip_thumb_image" or field_name == "filmstrip_image": params["filmstrip"] = True - else: url = urllib.parse.urlunparse((self.config.scheme, self.config.server, "/upload/upload_file", None, None, None)) @@ -2606,7 +2588,7 @@ def _upload_to_sg(self, entity_type, entity_id, path, field_name, display_name, if tag_list: params["tag_list"] = tag_list - params["file"] = open(path_to_open, "rb") + params["file"] = open(path, "rb") result = self._send_form(url, params) diff --git "a/tests/No\303\253l.jpg" "b/tests/No\303\253l\343\201\224.jpg" similarity index 100% rename from "tests/No\303\253l.jpg" rename to "tests/No\303\253l\343\201\224.jpg" diff --git a/tests/test_api.py b/tests/test_api.py index 4fdaab038..2866628a3 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -243,7 +243,7 @@ def test_upload_download(self): # test upload of non-ascii, unicode path u_path = os.path.abspath( os.path.expanduser( - glob.glob(os.path.join(six.text_type(this_dir), u'No*l.jpg'))[0] + glob.glob(os.path.join(six.text_type(this_dir), "Noëlご.jpg"))[0] ) ) @@ -310,6 +310,67 @@ def test_upload_download(self): # cleanup os.remove(file_path) + @patch('shotgun_api3.Shotgun._send_form') + def test_upload_to_sg(self, mock_send_form): + """ + Upload an attachment tests for _upload_to_sg() + """ + if "localhost" in self.server_url: + self.skipTest("upload / down tests skipped for localhost") + + self.sg.server_info["s3_direct_uploads_enabled"] = False + mock_send_form.method.assert_called_once() + mock_send_form.return_value = "1\n:123\nasd" + this_dir, _ = os.path.split(__file__) + u_path = os.path.abspath( + os.path.expanduser( + glob.glob(os.path.join(six.text_type(this_dir), "Noëlご.jpg"))[0] + ) + ) + upload_id = self.sg.upload( + "Ticket", + self.ticket['id'], + u_path, + 'attachments', + tag_list="monkeys, everywhere, send, help" + ) + mock_send_form_args, _ = mock_send_form.call_args + display_name_to_send = mock_send_form_args[1].get("display_name", "") + self.assertTrue(isinstance(upload_id, int)) + self.assertFalse( + display_name_to_send.startswith("b'") and + display_name_to_send.endswith("'") + ) + + upload_id = self.sg.upload( + "Ticket", + self.ticket['id'], + u_path, + 'filmstrip_image', + tag_list="monkeys, everywhere, send, help", + ) + self.assertTrue(isinstance(upload_id, int)) + mock_send_form_args, _ = mock_send_form.call_args + display_name_to_send = mock_send_form_args[1].get("display_name", "") + self.assertTrue(isinstance(upload_id, int)) + self.assertFalse( + display_name_to_send.startswith("b'") and + display_name_to_send.endswith("'") + ) + + mock_send_form.method.assert_called_once() + mock_send_form.return_value = "2\nIt can't be upload" + self.assertRaises( + shotgun_api3.ShotgunError, + self.sg.upload, + "Ticket", + self.ticket['id'], + u_path, + 'attachments', + tag_list="monkeys, everywhere, send, help" + ) + self.sg.server_info["s3_direct_uploads_enabled"] = True + def test_upload_thumbnail_in_create(self): """Upload a thumbnail via the create method""" this_dir, _ = os.path.split(__file__) From 0323312a06294c9adfa471ddacb322a57a467801 Mon Sep 17 00:00:00 2001 From: Julien Langlois <16244608+julien-lang@users.noreply.github.com> Date: Thu, 6 Jun 2024 10:14:00 -0700 Subject: [PATCH 060/125] SG-35529 Minor code refactoring: Clarify the use of _build_opener in download_attachment (#343) Minor code refactoring: move the _build_opener call from set_up_auth_cookie to download_attachment --- docs/reference.rst | 2 +- shotgun_api3/shotgun.py | 22 ++++++++++++---------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/docs/reference.rst b/docs/reference.rst index 8c9e6af6f..da171ea85 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -117,7 +117,7 @@ control, these methods are available. .. automethod:: Shotgun.close .. automethod:: Shotgun.authenticate_human_user .. automethod:: Shotgun.get_session_token -.. automethod:: Shotgun.set_up_auth_cookie +.. automethod:: Shotgun.get_auth_cookie_handler .. automethod:: Shotgun.add_user_agent .. automethod:: Shotgun.reset_user_agent .. automethod:: Shotgun.set_session_uuid diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index 5e4595299..392c63a26 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -2702,14 +2702,16 @@ def download_attachment(self, attachment=False, file_path=None, attachment_id=No if url is None: return None - # We only need to set the auth cookie for downloads from Shotgun server + cookie_handler = None if self.config.server in url: - self.set_up_auth_cookie() + # We only need to set the auth cookie for downloads from Shotgun server + cookie_handler = self.get_auth_cookie_handler() + opener = self._build_opener(cookie_handler) try: request = urllib.request.Request(url) request.add_header("user-agent", "; ".join(self._user_agents)) - req = urllib.request.urlopen(request) + req = opener.open(request) if file_path: shutil.copyfileobj(req, fp) else: @@ -2750,21 +2752,21 @@ def download_attachment(self, attachment=False, file_path=None, attachment_id=No else: return attachment - def set_up_auth_cookie(self): + def get_auth_cookie_handler(self): """ - Set up urllib2 with a cookie for authentication on the Shotgun instance. + Return an urllib cookie handler containing a cookie for FPTR + authentication. - Looks up session token and sets that in a cookie in the :mod:`urllib2` handler. This is - used internally for downloading attachments from the Shotgun server. + Looks up session token and sets that in a cookie in the :mod:`urllib2` + handler. + This is used internally for downloading attachments from FPTR. """ sid = self.get_session_token() cj = http_cookiejar.LWPCookieJar() c = http_cookiejar.Cookie("0", "_session_id", sid, None, False, self.config.server, False, False, "/", True, False, None, True, None, None, {}) cj.set_cookie(c) - cookie_handler = urllib.request.HTTPCookieProcessor(cj) - opener = self._build_opener(cookie_handler) - urllib.request.install_opener(opener) + return urllib.request.HTTPCookieProcessor(cj) def get_attachment_download_url(self, attachment): """ From f3a2230d00207427185aa97e13bc942e6873b465 Mon Sep 17 00:00:00 2001 From: Julien Langlois <16244608+julien-lang@users.noreply.github.com> Date: Fri, 7 Jun 2024 12:52:18 -0700 Subject: [PATCH 061/125] SG-34910 Fixup SSLEOFError exception (#346) * Minor fixup to make sure the attempt log shows up in any cases * Add missing log * Fixup close the connection and attempt a new request in case of SSLEOFError * Remove deprecated code since the ssl module was introduced in Python 2.6 * Add a test for _make_call retry --- shotgun_api3/shotgun.py | 37 ++++++++++------ tests/test_api.py | 96 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+), 13 deletions(-) diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index 392c63a26..84060f25f 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -42,6 +42,7 @@ import os import re import copy +import ssl import stat # used for attachment upload import sys import time @@ -111,14 +112,6 @@ def _is_mimetypes_broken(): have a self-signed internal certificate that isn't included in our certificate bundle, you may not require the added security provided by enforcing this. """ -try: - import ssl -except ImportError as e: - if "SHOTGUN_FORCE_CERTIFICATE_VALIDATION" in os.environ: - raise ImportError("%s. SHOTGUN_FORCE_CERTIFICATE_VALIDATION environment variable prevents " - "disabling SSL certificate validation." % e) - LOG.debug("ssl not found, disabling certificate validation") - NO_SSL_VALIDATION = True # ---------------------------------------------------------------------------- # Version @@ -3577,6 +3570,22 @@ def _make_call(self, verb, path, body, headers): attempt += 1 try: return self._http_request(verb, path, body, req_headers) + except ssl.SSLEOFError as e: + # SG-34910 - EOF occurred in violation of protocol (_ssl.c:2426) + # This issue seems to be related to proxy and keep alive. + # It looks like, sometimes, the proxy drops the connection on + # the TCP/TLS level despites the keep-alive. So we need to close + # the connection and make a new attempt. + LOG.debug("SSLEOFError: {}".format(e)) + self._close_connection() + if attempt == max_rpc_attempts: + LOG.debug("Request failed. Giving up after %d attempts." % attempt) + raise + # This is the exact same block as the "except Exception" bellow. + # We need to do it here because the next except will match it + # otherwise and will not re-attempt. + # When we drop support of Python 2 and we will probably drop the + # next except, we might want to remove this except too. except ssl_error_classes as e: # Test whether the exception is due to the fact that this is an older version of # Python that cannot validate certificates encrypted with SHA-2. If it is, then @@ -3608,17 +3617,19 @@ def _make_call(self, verb, path, body, headers): self._close_connection() if attempt == max_rpc_attempts: + LOG.debug("Request failed. Giving up after %d attempts." % attempt) raise except Exception: self._close_connection() if attempt == max_rpc_attempts: LOG.debug("Request failed. Giving up after %d attempts." % attempt) raise - LOG.debug( - "Request failed, attempt %d of %d. Retrying in %.2f seconds..." % - (attempt, max_rpc_attempts, rpc_attempt_interval) - ) - time.sleep(rpc_attempt_interval) + + LOG.debug( + "Request failed, attempt %d of %d. Retrying in %.2f seconds..." % + (attempt, max_rpc_attempts, rpc_attempt_interval) + ) + time.sleep(rpc_attempt_interval) def _http_request(self, verb, path, body, headers): """ diff --git a/tests/test_api.py b/tests/test_api.py index 2866628a3..62b91ad7a 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -18,7 +18,9 @@ import datetime import sys import os +from . import mock from .mock import patch, MagicMock +import ssl import time import types import uuid @@ -1900,6 +1902,100 @@ def test_status_not_200(self, mock_request): mock_request.return_value = (response, {}) self.assertRaises(shotgun_api3.ProtocolError, self.sg.find_one, 'Shot', []) + @patch('shotgun_api3.shotgun.Http.request') + def test_make_call_retry(self, mock_request): + response = MagicMock(name="response mock", spec=dict) + response.status = 200 + response.reason = 'reason' + mock_request.return_value = (response, {}) + + bak_rpc_attempt_interval = self.sg.config.rpc_attempt_interval + self.sg.config.rpc_attempt_interval = 0 + try: + # First: make the request raise a consistent exception + mock_request.side_effect = Exception("not working") + with self.assertLogs( + 'shotgun_api3', level='DEBUG' + ) as cm1, self.assertRaises( + Exception + ) as cm2: + self.sg.info() + + self.assertEqual(cm2.exception.args[0], "not working") + log_content = "\n".join(cm1.output) + for i in [1,2]: + self.assertIn( + f"Request failed, attempt {i} of 3. Retrying", + log_content, + ) + self.assertIn( + "Request failed. Giving up after 3 attempts.", + log_content, + ) + + # Then, make the exception happening only once and prove the + # retry works + def my_side_effect(*args, **kwargs): + try: + if my_side_effect.counter<1: + raise Exception("not working") + + return mock.DEFAULT + finally: + my_side_effect.counter += 1 + + my_side_effect.counter = 0 + mock_request.side_effect = my_side_effect + with self.assertLogs('shotgun_api3', level='DEBUG') as cm: + self.assertIsInstance( + self.sg.info(), + dict, + ) + + log_content = "\n".join(cm.output) + self.assertIn( + "Request failed, attempt 1 of 3. Retrying", + log_content, + ) + self.assertNotIn( + "Request failed, attempt 2 of 3. Retrying", + log_content, + ) + + # Last: raise a SSLEOFError exception - SG-34910 + def my_side_effect2(*args, **kwargs): + try: + if my_side_effect2.counter<1: + raise ssl.SSLEOFError( + "EOF occurred in violation of protocol (_ssl.c:2426)" + ) + + return mock.DEFAULT + finally: + my_side_effect2.counter += 1 + + my_side_effect2.counter = 0 + mock_request.side_effect = my_side_effect2 + + with self.assertLogs('shotgun_api3', level='DEBUG') as cm: + self.assertIsInstance( + self.sg.info(), + dict, + ) + + log_content = "\n".join(cm.output) + self.assertIn("SSLEOFError", log_content) + self.assertIn( + "Request failed, attempt 1 of 3. Retrying", + log_content, + ) + self.assertNotIn( + "Request failed, attempt 2 of 3. Retrying", + log_content, + ) + finally: + self.sg.config.rpc_attempt_interval = bak_rpc_attempt_interval + @patch('shotgun_api3.shotgun.Http.request') def test_sha2_error(self, mock_request): # Simulate the exception raised with SHA-2 errors From 74c8047d304710bbe9b1a33d950e0e4da974f82c Mon Sep 17 00:00:00 2001 From: Eduardo Chauca <166560435+eduardoChaucaGallegos@users.noreply.github.com> Date: Thu, 13 Jun 2024 17:55:03 -0500 Subject: [PATCH 062/125] Packaging for v3.6.1 (#348) * Packaging for v3.6.1 --- HISTORY.rst | 7 +++++++ setup.py | 2 +- shotgun_api3/shotgun.py | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index ca83eae40..3b7dd4d95 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -4,6 +4,13 @@ Flow Production Tracking Python API Changelog Here you can see the full list of changes between each Python API release. +v3.6.1 (2024 Jun 6) +=================== +- Adds multi_entity_update_modes support to mockgun ``update()`` and ``batch()`` methods. +- Implements a retry strategy only when encountering an URLError or SSLEOFError. +- Fixes the issue with deleting prefix and suffix for ``display_name`` variable at the moment of upload for a local install. +- Clarifies the use of ``_build_opener`` in ``download_attachment()``. + v3.6.0 (2024 May 1) =================== - Drop support for Python 2.7 diff --git a/setup.py b/setup.py index 12653f21e..a0f51e764 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ setup( name='shotgun_api3', - version='3.6.0', + version='3.6.1', description='Flow Production Tracking Python API', long_description=readme, author='Autodesk', diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index 84060f25f..4d65be51a 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -115,7 +115,7 @@ def _is_mimetypes_broken(): # ---------------------------------------------------------------------------- # Version -__version__ = "3.6.0" +__version__ = "3.6.1" # ---------------------------------------------------------------------------- # Errors From fc9eff160936c1fe0e4511f7eded49b424b05d4a Mon Sep 17 00:00:00 2001 From: Carlos Villavicencio <123113322+carlos-villavicencio-adsk@users.noreply.github.com> Date: Wed, 19 Jun 2024 17:02:05 -0500 Subject: [PATCH 063/125] SG-35018 Remove Ticket entity reference and prepare this to run in CI (#349) * Remove Ticket entity related tests * Remove config * Clean-up * Update tests to switch from attachment to sg_uploaded_movie * Revert removed tests * Add Jenkins setting --- .gitignore | 1 + azure-pipelines-templates/run-tests.yml | 1 - tests/base.py | 29 ++++++------- tests/example_config | 1 - tests/test_api.py | 58 ++++++++++++------------- 5 files changed, 43 insertions(+), 47 deletions(-) diff --git a/.gitignore b/.gitignore index f8adaad52..3e6ff329a 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ tests/config .travis-solo htmlcov test-output.xml +coverage.xml # setup related build diff --git a/azure-pipelines-templates/run-tests.yml b/azure-pipelines-templates/run-tests.yml index 34345a332..8fb33a6d6 100644 --- a/azure-pipelines-templates/run-tests.yml +++ b/azure-pipelines-templates/run-tests.yml @@ -129,7 +129,6 @@ jobs: SG_VERSION_CODE: CI-$(python_api_human_login)-${{parameters.name}}-$(python.version) SG_SHOT_CODE: CI-$(python_api_human_login)-${{parameters.name}}-$(python.version) SG_TASK_CONTENT: CI-$(python_api_human_login)-${{parameters.name}}-$(python.version) - SG_TICKET_TILE: CI-$(python_api_human_login)-${{parameters.name}}-$(python.version) SG_PLAYLIST_CODE: CI-$(python_api_human_login)-${{parameters.name}}-$(python.version) # Upload the code coverage result to codecov.io. diff --git a/tests/base.py b/tests/base.py index 28547cbeb..900b95998 100644 --- a/tests/base.py +++ b/tests/base.py @@ -39,7 +39,6 @@ class TestBase(unittest.TestCase): note = None playlist = None task = None - ticket = None human_password = None server_url = None server_address = None @@ -227,9 +226,6 @@ def _setup_mock_data(self): self.version = {'id': 5, 'code': self.config.version_code, 'type': 'Version'} - self.ticket = {'id': 6, - 'title': self.config.ticket_title, - 'type': 'Ticket'} self.playlist = {'id': 7, 'code': self.config.playlist_code, 'type': 'Playlist'} @@ -264,11 +260,18 @@ def setUpClass(cls): # When running the tests from a pull request from a client, the Shotgun # site URL won't be set, so do not attempt to connect to Shotgun. if cls.config.server_url: - sg = api.Shotgun( - cls.config.server_url, - cls.config.script_name, - cls.config.api_key - ) + if cls.config.jenkins: + sg = api.Shotgun( + cls.config.server_url, + login=cls.config.human_login, + password=cls.config.human_password + ) + else: + sg = api.Shotgun( + cls.config.server_url, + cls.config.script_name, + cls.config.api_key + ) cls.sg_version = tuple(sg.info()['version'][:3]) cls._setup_db(cls.config, sg) @@ -330,12 +333,6 @@ def _setup_db(cls, config, sg): 'sg_status_list': 'ip'} cls.task = _find_or_create_entity(sg, 'Task', data, keys) - data = {'project': cls.project, - 'title': cls.config.ticket_title, - 'sg_priority': '3'} - keys = ['title', 'project', 'sg_priority'] - cls.ticket = _find_or_create_entity(sg, 'Ticket', data, keys) - keys = ['code'] data = {'code': 'api wrapper test storage', 'mac_path': 'nowhere', @@ -406,7 +403,7 @@ def config_keys(self): 'api_key', 'asset_code', 'http_proxy', 'human_login', 'human_name', 'human_password', 'mock', 'project_name', 'script_name', 'server_url', 'session_uuid', 'shot_code', 'task_content', - 'version_code', 'playlist_code', 'ticket_title' + 'version_code', 'playlist_code', 'jenkins' ] def read_config(self, config_path): diff --git a/tests/example_config b/tests/example_config index 064eaba6a..cb04aefab 100644 --- a/tests/example_config +++ b/tests/example_config @@ -34,5 +34,4 @@ asset_code : Sg unittest asset version_code : Sg unittest version shot_code : Sg unittest shot task_content : Sg unittest task -ticket_title : Sg unittest ticket playlist_code : Sg unittest playlist diff --git a/tests/test_api.py b/tests/test_api.py index 62b91ad7a..9186d4430 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -171,8 +171,8 @@ def test_upload_download(self): os.path.join(this_dir, "sg_logo.jpg"))) size = os.stat(path).st_size - attach_id = self.sg.upload("Ticket", - self.ticket['id'], path, 'attachments', + attach_id = self.sg.upload("Version", + self.version['id'], path, 'sg_uploaded_movie', tag_list="monkeys, everywhere, send, help") # test download with attachment_id @@ -201,12 +201,12 @@ def test_upload_download(self): self.assertEqual(orig_file, attach_file) # test download with attachment hash - ticket = self.sg.find_one('Ticket', [['id', 'is', self.ticket['id']]], - ['attachments']) + version = self.sg.find_one('Version', [['id', 'is', self.version['id']]], + ['sg_uploaded_movie']) # Look for the attachment we just uploaded, the attachments are not returned from latest # to earliest. - attachment = [x for x in ticket["attachments"] if x["id"] == attach_id] + attachment = [v for k, v in version["sg_uploaded_movie"].items() if (k, v) == ("id", attach_id)] self.assertEqual(len(attachment), 1) attachment = attachment[0] @@ -254,10 +254,10 @@ def test_upload_download(self): # only checking that the non-ascii string encoding doesn't trip # us up the way it used to. self.sg.upload( - "Ticket", - self.ticket['id'], + "Version", + self.version['id'], u_path, - 'attachments', + 'sg_uploaded_movie', tag_list="monkeys, everywhere, send, help" ) @@ -266,10 +266,10 @@ def test_upload_download(self): # primarily a concern on Windows, as it doesn't handle that # situation as well as OS X and Linux. self.sg.upload( - "Ticket", - self.ticket['id'], + "Version", + self.version['id'], u_path.encode("utf-8"), - 'attachments', + 'sg_uploaded_movie', tag_list="monkeys, everywhere, send, help" ) if six.PY2: @@ -290,19 +290,19 @@ def test_upload_download(self): self.assertRaises( shotgun_api3.ShotgunError, self.sg.upload, - "Ticket", - self.ticket['id'], + "Version", + self.version['id'], file_path_u.encode("shift-jis"), - 'attachments', + 'sg_uploaded_movie', tag_list="monkeys, everywhere, send, help" ) # But it should work in all cases if a unicode string is used. self.sg.upload( - "Ticket", - self.ticket['id'], + "Version", + self.version['id'], file_path_u, - 'attachments', + 'sg_uploaded_movie', tag_list="monkeys, everywhere, send, help" ) @@ -330,8 +330,8 @@ def test_upload_to_sg(self, mock_send_form): ) ) upload_id = self.sg.upload( - "Ticket", - self.ticket['id'], + "Version", + self.version['id'], u_path, 'attachments', tag_list="monkeys, everywhere, send, help" @@ -345,8 +345,8 @@ def test_upload_to_sg(self, mock_send_form): ) upload_id = self.sg.upload( - "Ticket", - self.ticket['id'], + "Version", + self.version['id'], u_path, 'filmstrip_image', tag_list="monkeys, everywhere, send, help", @@ -365,8 +365,8 @@ def test_upload_to_sg(self, mock_send_form): self.assertRaises( shotgun_api3.ShotgunError, self.sg.upload, - "Ticket", - self.ticket['id'], + "Version", + self.version['id'], u_path, 'attachments', tag_list="monkeys, everywhere, send, help" @@ -1525,30 +1525,30 @@ def test_in_relation_comma_list(self): """ Test that 'in' relation using commas (old format) works with list fields. """ - filters = [['sg_priority', 'in', self.ticket['sg_priority'], '1'], + filters = [['frame_count', 'in', self.version['frame_count'], 33], ['project', 'is', self.project]] - result = self._id_in_result('Ticket', filters, self.ticket['id']) + result = self._id_in_result('Version', filters, self.version['id']) self.assertTrue(result) def test_in_relation_list_list(self): """ Test that 'in' relation using list (new format) works with list fields. """ - filters = [['sg_priority', 'in', [self.ticket['sg_priority'], '1']], + filters = [['frame_count', 'in', [self.version['frame_count'], 33]], ['project', 'is', self.project]] - result = self._id_in_result('Ticket', filters, self.ticket['id']) + result = self._id_in_result('Version', filters, self.version['id']) self.assertTrue(result) def test_not_in_relation_list(self): """ Test that 'not_in' relation using commas (old format) works with list fields. """ - filters = [['sg_priority', 'not_in', [self.ticket['sg_priority'], '1']], + filters = [['frame_count', 'not_in', [self.version['frame_count'], 33]], ['project', 'is', self.project]] - result = self._id_in_result('Ticket', filters, self.ticket['id']) + result = self._id_in_result('Version', filters, self.version['id']) self.assertFalse(result) def test_in_relation_comma_multi_entity(self): From bd5245d4efeea544b5238d7d36a3da083237c404 Mon Sep 17 00:00:00 2001 From: Carlos Villavicencio <123113322+carlos-villavicencio-adsk@users.noreply.github.com> Date: Thu, 27 Jun 2024 14:10:45 -0500 Subject: [PATCH 064/125] SG-35018 Condition auth for Jenkins environment (#350) * Condition auth for Jenkins environment * Add prints * Add more debug logic * Use session token for Jenkins * Add debug info * More debug info * Revert f4e51cbbc0b553f4b628073caf31c3e763bad341 * Remove redundant code * Revert * Unskip old tests. Skip datetime tests for Jenkins. * Condition test for SG_JENKINS * Improve test * Revert unskip * Add durations to pytest * Address feedback * Refactor find_one_await_thumbnail invocation * Remove duplicate line * Unskip test * Update .coveragerc * Addressing feedback --- .coveragerc | 2 + azure-pipelines-templates/run-tests.yml | 2 +- tests/base.py | 52 +++++--- tests/test_api.py | 155 ++++++++++++------------ tests/test_api_long.py | 1 - 5 files changed, 117 insertions(+), 95 deletions(-) diff --git a/.coveragerc b/.coveragerc index 658a98112..97715b18d 100644 --- a/.coveragerc +++ b/.coveragerc @@ -17,3 +17,5 @@ source=shotgun_api3 omit= shotgun_api3/lib/httplib2/* shotgun_api3/lib/six.py + shotgun_api3/lib/certify/* + shotgun_api3/lib/pyparsing.py diff --git a/azure-pipelines-templates/run-tests.yml b/azure-pipelines-templates/run-tests.yml index 8fb33a6d6..551c06989 100644 --- a/azure-pipelines-templates/run-tests.yml +++ b/azure-pipelines-templates/run-tests.yml @@ -96,7 +96,7 @@ jobs: # for example 'Windows - 2.7' - bash: | cp ./tests/example_config ./tests/config - pytest -v --cov shotgun_api3 --cov-report xml --test-run-title="${{parameters.name}}-$(python.version)" + pytest --durations=0 -v --cov shotgun_api3 --cov-report xml --test-run-title="${{parameters.name}}-$(python.version)" displayName: Running tests env: # Pass the values needed to authenticate with the Flow Production Tracking site and create some entities. diff --git a/tests/base.py b/tests/base.py index 900b95998..04e52aa72 100644 --- a/tests/base.py +++ b/tests/base.py @@ -3,6 +3,7 @@ import os import random import re +import time import unittest from . import mock @@ -26,6 +27,11 @@ def skip(f): return lambda self: None +THUMBNAIL_MAX_ATTEMPTS = 30 +THUMBNAIL_RETRY_INTERVAL = 10 +TRANSIENT_IMAGE_PATH = "images/status/transient" + + class TestBase(unittest.TestCase): '''Base class for tests. @@ -59,6 +65,14 @@ def setUpClass(cls): cur_folder = os.path.dirname(os.path.abspath(__file__)) config_path = os.path.join(cur_folder, "config") cls.config.read_config(config_path) + if cls.config.jenkins: + cls.auth_args = dict( + login=cls.config.human_login, password=cls.config.human_password + ) + else: + cls.auth_args = dict( + script_name=cls.config.script_name, api_key=cls.config.api_key + ) def setUp(self, auth_mode='ApiUser'): # When running the tests from a pull request from a client, the Shotgun @@ -90,9 +104,8 @@ def setUp(self, auth_mode='ApiUser'): # first make an instance based on script key/name so # we can generate a session token sg = api.Shotgun(self.config.server_url, - self.config.script_name, - self.config.api_key, - http_proxy=self.config.http_proxy) + http_proxy=self.config.http_proxy, + **self.auth_args) self.session_token = sg.get_session_token() # now log in using session token self.sg = api.Shotgun(self.config.server_url, @@ -234,7 +247,9 @@ def _setup_mock_data(self): class LiveTestBase(TestBase): '''Test base for tests relying on connection to server.''' - def setUp(self, auth_mode='ApiUser'): + def setUp(self, auth_mode=None): + if not auth_mode: + auth_mode = 'HumanUser' if self.config.jenkins else 'ApiUser' super(LiveTestBase, self).setUp(auth_mode) if self.sg.server_caps.version and \ self.sg.server_caps.version >= (3, 3, 0) and \ @@ -260,18 +275,10 @@ def setUpClass(cls): # When running the tests from a pull request from a client, the Shotgun # site URL won't be set, so do not attempt to connect to Shotgun. if cls.config.server_url: - if cls.config.jenkins: - sg = api.Shotgun( - cls.config.server_url, - login=cls.config.human_login, - password=cls.config.human_password - ) - else: - sg = api.Shotgun( - cls.config.server_url, - cls.config.script_name, - cls.config.api_key - ) + sg = api.Shotgun( + cls.config.server_url, + **cls.auth_args, + ) cls.sg_version = tuple(sg.info()['version'][:3]) cls._setup_db(cls.config, sg) @@ -365,6 +372,19 @@ def gen_entity(self, entity_type, **kwargs): rv = self.sg.delete(entity_type, entity["id"]) assert rv == True + def find_one_await_thumbnail(self, entity_type, filters, fields=["image"], thumbnail_field_name="image", **kwargs): + attempts = 0 + while attempts < THUMBNAIL_MAX_ATTEMPTS: + result = self.sg.find_one(entity_type, filters, fields=fields, **kwargs) + if TRANSIENT_IMAGE_PATH in result.get(thumbnail_field_name, ""): + return result + + time.sleep(THUMBNAIL_RETRY_INTERVAL) + attempts += 1 + else: + if self.config.jenkins: + self.skipTest("Jenkins test timed out waiting for thumbnail") + class HumanUserAuthLiveTestBase(LiveTestBase): ''' diff --git a/tests/test_api.py b/tests/test_api.py index 9186d4430..9fbc7a678 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -40,10 +40,6 @@ from . import base -THUMBNAIL_MAX_ATTEMPTS = 30 -THUMBNAIL_RETRY_INTERAL = 10 -TRANSIENT_IMAGE_PATH = "images/status/transient" - class TestShotgunApi(base.LiveTestBase): def setUp(self): @@ -317,9 +313,6 @@ def test_upload_to_sg(self, mock_send_form): """ Upload an attachment tests for _upload_to_sg() """ - if "localhost" in self.server_url: - self.skipTest("upload / down tests skipped for localhost") - self.sg.server_info["s3_direct_uploads_enabled"] = False mock_send_form.method.assert_called_once() mock_send_form.return_value = "1\n:123\nasd" @@ -383,11 +376,10 @@ def test_upload_thumbnail_in_create(self): data = {'image': path, 'code': 'Test Version', 'project': self.project} new_version = self.sg.create("Version", data, return_fields=['image']) - new_version = find_one_await_thumbnail( - self.sg, + new_version = self.find_one_await_thumbnail( "Version", [["id", "is", new_version["id"]]], - fields=["image", "project", "type", "id"] + fields=["image", "project", "type", "id"], ) self.assertTrue(new_version is not None) @@ -434,7 +426,9 @@ def test_upload_thumbnail_for_version(self): # check result on version version_with_thumbnail = self.sg.find_one('Version', [['id', 'is', self.version['id']]]) - version_with_thumbnail = find_one_await_thumbnail(self.sg, 'Version', [['id', 'is', self.version['id']]]) + version_with_thumbnail = self.find_one_await_thumbnail( + "Version", [["id", "is", self.version["id"]]] + ) self.assertEqual(version_with_thumbnail.get('type'), 'Version') self.assertEqual(version_with_thumbnail.get('id'), self.version['id']) @@ -461,7 +455,9 @@ def test_upload_thumbnail_for_task(self): # check result on version task_with_thumbnail = self.sg.find_one('Task', [['id', 'is', self.task['id']]]) - task_with_thumbnail = find_one_await_thumbnail(self.sg, 'Task', [['id', 'is', self.task['id']]]) + task_with_thumbnail = self.find_one_await_thumbnail( + "Task", [["id", "is", self.task["id"]]] + ) self.assertEqual(task_with_thumbnail.get('type'), 'Task') self.assertEqual(task_with_thumbnail.get('id'), self.task['id']) @@ -557,12 +553,11 @@ def test_linked_thumbnail_url(self): thumb_id = self.sg.upload_thumbnail("Project", self.version['project']['id'], path) - response_version_with_project = find_one_await_thumbnail( - self.sg, + response_version_with_project = self.find_one_await_thumbnail( "Version", [["id", "is", self.version["id"]]], fields=["id", "code", "project.Project.image"], - thumbnail_field_name="project.Project.image" + thumbnail_field_name="project.Project.image", ) if self.sg.server_caps.version and self.sg.server_caps.version >= (3, 3, 0): @@ -597,12 +592,12 @@ def share_thumbnail_retry(*args, **kwargs): # the thumbnail to finish processing. thumbnail_id = None attempts = 0 - while attempts < THUMBNAIL_MAX_ATTEMPTS and thumbnail_id is None: + while attempts < base.THUMBNAIL_MAX_ATTEMPTS and thumbnail_id is None: try: thumbnail_id = self.sg.share_thumbnail(*args, **kwargs) attempts += 1 except shotgun_api3.ShotgunError: - time.sleep(THUMBNAIL_RETRY_INTERAL) + time.sleep(base.THUMBNAIL_RETRY_INTERVAL) return thumbnail_id this_dir, _ = os.path.split(__file__) @@ -610,17 +605,15 @@ def share_thumbnail_retry(*args, **kwargs): # upload thumbnail to first entity and share it with the rest share_thumbnail_retry([self.version, self.shot], thumbnail_path=path) - response_version_thumbnail = find_one_await_thumbnail( - self.sg, + response_version_thumbnail = self.find_one_await_thumbnail( 'Version', [['id', 'is', self.version['id']]], - fields=['id', 'code', 'image'] + fields=['id', 'code', 'image'], ) - response_shot_thumbnail = find_one_await_thumbnail( - self.sg, + response_shot_thumbnail = self.find_one_await_thumbnail( 'Shot', [['id', 'is', self.shot['id']]], - fields=['id', 'code', 'image'] + fields=['id', 'code', 'image'], ) shot_url = urllib.parse.urlparse(response_shot_thumbnail.get('image')) @@ -632,23 +625,20 @@ def share_thumbnail_retry(*args, **kwargs): # share thumbnail from source entity with entities self.sg.upload_thumbnail("Version", self.version['id'], path) share_thumbnail_retry([self.asset, self.shot], source_entity=self.version) - response_version_thumbnail = find_one_await_thumbnail( - self.sg, + response_version_thumbnail = self.find_one_await_thumbnail( 'Version', [['id', 'is', self.version['id']]], - fields=['id', 'code', 'image'] + fields=['id', 'code', 'image'], ) - response_shot_thumbnail = find_one_await_thumbnail( - self.sg, + response_shot_thumbnail = self.find_one_await_thumbnail( 'Shot', [['id', 'is', self.shot['id']]], - fields=['id', 'code', 'image'] + fields=['id', 'code', 'image'], ) - response_asset_thumbnail = find_one_await_thumbnail( - self.sg, + response_asset_thumbnail = self.find_one_await_thumbnail( 'Asset', [['id', 'is', self.asset['id']]], - fields=['id', 'code', 'image'] + fields=['id', 'code', 'image'], ) shot_url = urllib.parse.urlparse(response_shot_thumbnail.get('image')) @@ -820,9 +810,8 @@ def test_summary_values(self): def test_ensure_ascii(self): '''test_ensure_ascii tests ensure_unicode flag.''' sg_ascii = shotgun_api3.Shotgun(self.config.server_url, - self.config.script_name, - self.config.api_key, - ensure_ascii=True) + ensure_ascii=True, + **self.auth_args) result = sg_ascii.find_one('Note', [['id', 'is', self.note['id']]], fields=['content']) if six.PY2: @@ -832,9 +821,8 @@ def test_ensure_ascii(self): def test_ensure_unicode(self): '''test_ensure_unicode tests ensure_unicode flag.''' sg_unicode = shotgun_api3.Shotgun(self.config.server_url, - self.config.script_name, - self.config.api_key, - ensure_ascii=False) + ensure_ascii=False, + **self.auth_args) result = sg_unicode.find_one('Note', [['id', 'is', self.note['id']]], fields=['content']) self.assertTrue(_has_unicode(result)) @@ -858,7 +846,6 @@ def test_work_schedule(self): self.assertRaises(shotgun_api3.ShotgunError, self.sg.work_schedule_read, start_date_obj, end_date_obj, project, user) - resp = self.sg.work_schedule_update('2012-01-02', False, 'Studio Holiday') expected = { 'date': '2012-01-02', @@ -907,11 +894,8 @@ def test_work_schedule(self): work_schedule['2012-01-04'] = {"reason": "USER_EXCEPTION", "working": False, "description": "Artist Holiday"} self.assertEqual(work_schedule, resp) - # For now disable tests that are erroneously failling on some sites to - # allow CI to pass until the known issue causing this is resolved. # test_preferences_read fails when preferences don't match the expected # preferences. - @base.skip("Skip test_preferences_read because preferences on test sites are mismatched.") def test_preferences_read(self): # Only run the tests on a server with the feature. if not self.sg.server_caps.version or self.sg.server_caps.version < (7, 10, 0): @@ -936,8 +920,9 @@ def test_preferences_read(self): 'format_number_fields': '1,000', 'format_time_hour_fields': '12 hour', 'hours_per_day': 8.0, - 'last_day_work_week': None, - 'support_local_storage': True + 'support_local_storage': True, + 'enable_rv_integration': True, + 'enable_shotgun_review_for_rv': False, } # Simply make sure viewmaster settings are there. These change frequently and we # don't want to have the test break because Viewmaster changed or because we didn't @@ -1010,6 +995,8 @@ def test_set_date(self): self.assertEqual(expected, actual) def test_set_date_time(self): + if self.config.jenkins: + self.skipTest("Jenkins. locked_until not updating.") entity = 'HumanUser' entity_id = self.human_user['id'] field_name = 'locked_until' @@ -1069,8 +1056,7 @@ def test_set_list(self): def test_set_multi_entity(self): sg = shotgun_api3.Shotgun(self.config.server_url, - self.config.script_name, - self.config.api_key) + **self.auth_args) keys = ['project', 'user', 'code'] data = {'project': self.project, 'user': self.human_user, @@ -1204,20 +1190,22 @@ def setUp(self): self.datetime_none = datetime.datetime(2008, 10, 13, 23, 10) def test_convert_to_utc(self): + if self.config.jenkins: + self.skipTest("Jenkins. locked_until not updating.") sg_utc = shotgun_api3.Shotgun(self.config.server_url, - self.config.script_name, - self.config.api_key, http_proxy=self.config.http_proxy, - convert_datetimes_to_utc=True) + convert_datetimes_to_utc=True, + **self.auth_args) self._assert_expected(sg_utc, self.datetime_none, self.datetime_local) self._assert_expected(sg_utc, self.datetime_local, self.datetime_local) def test_no_convert_to_utc(self): + if self.config.jenkins: + self.skipTest("Jenkins. locked_until not updating.") sg_no_utc = shotgun_api3.Shotgun(self.config.server_url, - self.config.script_name, - self.config.api_key, http_proxy=self.config.http_proxy, - convert_datetimes_to_utc=False) + convert_datetimes_to_utc=False, + **self.auth_args) self._assert_expected(sg_no_utc, self.datetime_none, self.datetime_none) self._assert_expected(sg_no_utc, self.datetime_utc, self.datetime_none) @@ -1853,6 +1841,10 @@ def test_following(self): class TestErrors(base.TestBase): + def setUp(self): + auth_mode = "HumanUser" if self.config.jenkins else "ApiUser" + super(TestErrors, self).setUp(auth_mode) + def test_bad_auth(self): '''test_bad_auth invalid script name or api key raises fault''' server_url = self.config.server_url @@ -2079,7 +2071,7 @@ def test_sanitized_auth_params(self, mock_open): # Try to upload a bogus file self.sg.upload('Note', 1234, thumbnail_path) except shotgun_api3.ShotgunError as e: - self.assertFalse(self.api_key in str(e)) + self.assertFalse(str(self.api_key) in str(e)) return # You should never get here... Otherwise some mocking failed and the @@ -2114,7 +2106,7 @@ def test_upload_missing_file(self): class TestScriptUserSudoAuth(base.LiveTestBase): def setUp(self): - super(TestScriptUserSudoAuth, self).setUp('ApiUser') + super(TestScriptUserSudoAuth, self).setUp() self.sg.update( 'HumanUser', @@ -2131,10 +2123,9 @@ def test_user_is_creator(self): return x = shotgun_api3.Shotgun(self.config.server_url, - self.config.script_name, - self.config.api_key, http_proxy=self.config.http_proxy, - sudo_as_login=self.config.human_login) + sudo_as_login=self.config.human_login, + **self.auth_args) data = { 'project': self.project, @@ -2161,6 +2152,8 @@ def test_human_user_sudo_auth_fails(self): Test 'sudo_as_login' option for HumanUser. Request fails on server because user has no permission to Sudo. """ + if self.config.jenkins: + self.skipTest("Jenkins. locked_until not updating.") if not self.sg.server_caps.version or self.sg.server_caps.version < (5, 3, 12): return @@ -2217,7 +2210,10 @@ def test_humanuser_upload_thumbnail_for_version(self): self.assertTrue(isinstance(thumb_id, int)) # check result on version - version_with_thumbnail = find_one_await_thumbnail(self.sg, 'Version', [['id', 'is', self.version['id']]]) + version_with_thumbnail = self.find_one_await_thumbnail( + "Version", + [["id", "is", self.version["id"]]], + ) self.assertEqual(version_with_thumbnail.get('type'), 'Version') self.assertEqual(version_with_thumbnail.get('id'), self.version['id']) @@ -2274,7 +2270,10 @@ def test_humanuser_upload_thumbnail_for_version(self): self.assertTrue(isinstance(thumb_id, int)) # check result on version - version_with_thumbnail = find_one_await_thumbnail(self.sg, 'Version', [['id', 'is', self.version['id']]]) + version_with_thumbnail = self.find_one_await_thumbnail( + "Version", + [["id", "is", self.version["id"]]], + ) self.assertEqual(version_with_thumbnail.get('type'), 'Version') self.assertEqual(version_with_thumbnail.get('id'), self.version['id']) @@ -2340,10 +2339,9 @@ def test_sudo_as_user(self): return sg = shotgun_api3.Shotgun(self.config.server_url, - self.config.script_name, - self.config.api_key, http_proxy=self.config.http_proxy, - sudo_as_login=self.config.human_login) + sudo_as_login=self.config.human_login, + **self.auth_args) initial = sg.find_one('Project', [['id', 'is', self.project['id']]], ['last_accessed_by_current_user']) time.sleep(1) @@ -2490,6 +2488,13 @@ def _check_note(self, data, note_id, additional_fields): note_data = self.sg.find_one("Note", [["id", "is", note_id]], list(expected_fields)) + # remove images before comparison + if ( + "created_by.HumanUser.image" in note_data + and "created_by.HumanUser.image" in data + ): + note_data.pop("created_by.HumanUser.image") + data.pop("created_by.HumanUser.image") self.assertEqual(note_data, data) def _check_reply(self, data, reply_id, additional_fields): @@ -2520,6 +2525,10 @@ def _check_attachment(self, data, attachment_id, additional_fields): [["id", "is", attachment_id]], list(expected_fields)) + # remove images before comparison + if "this_file" in attachment_data and "this_file" in data: + attachment_data["this_file"].pop("url") + data["this_file"].pop("url") self.assertEqual(attachment_data, data) # For now skip tests that are erroneously failling on some sites to @@ -2532,6 +2541,8 @@ def test_simple(self): if not self.sg.server_caps.version or self.sg.server_caps.version < (6, 2, 0): return + user_entity = "HumanUser" if self.config.jenkins else "ApiUser" + # create note note = self.sg.create("Note", {"content": "Test!", "project": self.project}) @@ -2542,21 +2553,21 @@ def test_simple(self): d = self.sg.find_one("Note", [["id", "is", note["id"]]], - ["created_by", "created_by.ApiUser.image"]) + ["created_by", f"created_by.{user_entity}.image"]) - current_thumbnail = d["created_by.ApiUser.image"] + current_thumbnail = d[f"created_by.{user_entity}.image"] if current_thumbnail is None: # upload thumbnail - self.sg.upload_thumbnail("ApiUser", + self.sg.upload_thumbnail(user_entity, d["created_by"]["id"], self._thumbnail_path) d = self.sg.find_one("Note", [["id", "is", note["id"]]], - ["created_by", "created_by.ApiUser.image"]) + ["created_by", f"created_by.{user_entity}.image"]) - current_thumbnail = d["created_by.ApiUser.image"] + current_thumbnail = d[f"created_by.{user_entity}.image"] # get thread result = self.sg.note_thread_read(note["id"]) @@ -3022,15 +3033,5 @@ def _get_path(url): return url.path -def find_one_await_thumbnail(sg, entity_type, filters, fields=["image"], thumbnail_field_name="image", **kwargs): - attempts = 0 - result = sg.find_one(entity_type, filters, fields=fields, **kwargs) - while attempts < THUMBNAIL_MAX_ATTEMPTS and TRANSIENT_IMAGE_PATH in result.get(thumbnail_field_name): - time.sleep(THUMBNAIL_RETRY_INTERAL) - result = sg.find_one(entity_type, filters, fields=fields, **kwargs) - attempts += 1 - return result - - if __name__ == '__main__': unittest.main() diff --git a/tests/test_api_long.py b/tests/test_api_long.py index 65f741cc4..0bf509b3c 100644 --- a/tests/test_api_long.py +++ b/tests/test_api_long.py @@ -96,7 +96,6 @@ def test_automated_find(self): limit = (limit % 5) + 1 page = (page % 3) + 1 - @base.skip("Skipping test due to CI failure. Too many database columns.") def test_schema(self): """Called schema functions""" From 9efa0a285a7642f7d01fba02ea3c94939f094404 Mon Sep 17 00:00:00 2001 From: Carlos Villavicencio <123113322+carlos-villavicencio-adsk@users.noreply.github.com> Date: Fri, 19 Jul 2024 08:05:52 -0500 Subject: [PATCH 065/125] Update certifi to 2024.7.4 (#353) --- shotgun_api3/lib/certifi/__init__.py | 2 +- shotgun_api3/lib/certifi/cacert.pem | 64 +++++++++++----------------- shotgun_api3/lib/requirements.txt | 2 +- 3 files changed, 26 insertions(+), 42 deletions(-) diff --git a/shotgun_api3/lib/certifi/__init__.py b/shotgun_api3/lib/certifi/__init__.py index 1c91f3ec9..d321f1bc3 100644 --- a/shotgun_api3/lib/certifi/__init__.py +++ b/shotgun_api3/lib/certifi/__init__.py @@ -1,4 +1,4 @@ from .core import contents, where __all__ = ["contents", "where"] -__version__ = "2024.02.02" +__version__ = "2024.07.04" diff --git a/shotgun_api3/lib/certifi/cacert.pem b/shotgun_api3/lib/certifi/cacert.pem index fac3c3190..a6581589b 100644 --- a/shotgun_api3/lib/certifi/cacert.pem +++ b/shotgun_api3/lib/certifi/cacert.pem @@ -3485,46 +3485,6 @@ DgQWBBQxCpCPtsad0kRLgLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ +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" @@ -4812,3 +4772,27 @@ 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----- diff --git a/shotgun_api3/lib/requirements.txt b/shotgun_api3/lib/requirements.txt index 6278cb3f1..34172e948 100644 --- a/shotgun_api3/lib/requirements.txt +++ b/shotgun_api3/lib/requirements.txt @@ -30,5 +30,5 @@ # released for our dependencies. httplib2==0.22.0 six==1.13.0 -certifi==2024.2.2 +certifi==2024.7.4 pyparsing==2.4.7 \ No newline at end of file From 0fef00f52675f4aabae708cb9c137a30832a02f4 Mon Sep 17 00:00:00 2001 From: Eduardo Chauca <166560435+eduardoChaucaGallegos@users.noreply.github.com> Date: Fri, 19 Jul 2024 12:44:11 -0500 Subject: [PATCH 066/125] SG-29997 - FIRST PHASE Python2 removing (#352) * ensure_* functions ans six types replaced by native and sgutils * improvement for ensure_str function --- shotgun_api3/lib/mockgun/mockgun.py | 11 +++-- shotgun_api3/lib/sgutils.py | 62 +++++++++++++++++++++++++++ shotgun_api3/shotgun.py | 65 +++++++++++++++-------------- tests/base.py | 4 +- tests/test_client.py | 18 ++++---- 5 files changed, 111 insertions(+), 49 deletions(-) create mode 100644 shotgun_api3/lib/sgutils.py diff --git a/shotgun_api3/lib/mockgun/mockgun.py b/shotgun_api3/lib/mockgun/mockgun.py index 4bf1e4bf4..36b98dd5d 100644 --- a/shotgun_api3/lib/mockgun/mockgun.py +++ b/shotgun_api3/lib/mockgun/mockgun.py @@ -120,7 +120,6 @@ from ...shotgun import _Config from .errors import MockgunError from .schema import SchemaFactory -from .. import six # ---------------------------------------------------------------------------- # Version @@ -505,14 +504,14 @@ def _validate_entity_data(self, entity_type, data): "float": float, "checkbox": bool, "percent": int, - "text": six.string_types, + "text": str, "serializable": dict, - "entity_type": six.string_types, - "date": six.string_types, + "entity_type": str, + "date": str, "date_time": datetime.datetime, "duration": int, - "list": six.string_types, - "status_list": six.string_types, + "list": str, + "status_list": str, "url": dict}[sg_type] except KeyError: raise ShotgunError( diff --git a/shotgun_api3/lib/sgutils.py b/shotgun_api3/lib/sgutils.py new file mode 100644 index 000000000..0d49e4b39 --- /dev/null +++ b/shotgun_api3/lib/sgutils.py @@ -0,0 +1,62 @@ +""" + ----------------------------------------------------------------------------- + Copyright (c) 2009-2024, Shotgun Software Inc. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + - Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + - Neither the name of the Shotgun Software Inc nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + + +def ensure_binary(s, encoding='utf-8', errors='strict'): + """ + Coerce **s** to bytes. + + - `str` -> encoded to `bytes` + - `bytes` -> `bytes` + """ + if isinstance(s, str): + return s.encode(encoding, errors) + elif isinstance(s, bytes): + return s + else: + raise TypeError(f"not expecting type '{type(s)}'") + + +def ensure_str(s, encoding='utf-8', errors='strict'): + """Coerce *s* to `str`. + + - `str` -> `str` + - `bytes` -> decoded to `str` + """ + if isinstance(s, str): + return s + + elif isinstance(s, bytes): + return s.decode(encoding, errors) + + raise TypeError(f"not expecting type '{type(s)}'") + + +ensure_text = ensure_str diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index 4d65be51a..f5c2c5209 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -32,6 +32,7 @@ # Python 2/3 compatibility from .lib import six from .lib import sgsix +from .lib import sgutils from .lib.six import BytesIO # used for attachment upload from .lib.six.moves import map @@ -665,7 +666,7 @@ def __init__(self, # the lowercase version of the credentials. auth, self.config.server = self._split_url(base_url) if auth: - auth = base64encode(six.ensure_binary( + auth = base64encode(sgutils.ensure_binary( urllib.parse.unquote(auth))).decode("utf-8") self.config.authorization = "Basic " + auth.strip() @@ -2440,7 +2441,7 @@ def upload(self, entity_type, entity_id, path, field_name=None, display_name=Non # have to raise a sane exception. This will always work for ascii and utf-8 # encoded strings, but will fail on some others if the string includes non # ascii characters. - if not isinstance(path, six.text_type): + if not isinstance(path, str): try: path = path.decode("utf-8") except UnicodeDecodeError: @@ -2721,7 +2722,7 @@ def download_attachment(self, attachment=False, file_path=None, attachment_id=No elif e.code == 403: # Only parse the body if it is an Amazon S3 url. if url.find("s3.amazonaws.com") != -1 and e.headers["content-type"] == "application/xml": - body = [six.ensure_text(line) for line in e.readlines()] + body = [sgutils.ensure_text(line) for line in e.readlines()] if body: xml = "".join(body) # Once python 2.4 support is not needed we can think about using @@ -3545,7 +3546,7 @@ def _encode_payload(self, payload): """ wire = json.dumps(payload, ensure_ascii=False) - return six.ensure_binary(wire) + return sgutils.ensure_binary(wire) def _make_call(self, verb, path, body, headers): """ @@ -3720,8 +3721,8 @@ def _json_loads_ascii(self, body): def _decode_list(lst): newlist = [] for i in lst: - if isinstance(i, six.text_type): - i = six.ensure_str(i) + if isinstance(i, str): + i = sgutils.ensure_str(i) elif isinstance(i, list): i = _decode_list(i) newlist.append(i) @@ -3730,10 +3731,10 @@ def _decode_list(lst): def _decode_dict(dct): newdict = {} for k, v in six.iteritems(dct): - if isinstance(k, six.text_type): - k = six.ensure_str(k) - if isinstance(v, six.text_type): - v = six.ensure_str(v) + if isinstance(k, str): + k = sgutils.ensure_str(k) + if isinstance(v, str): + v = sgutils.ensure_str(v) elif isinstance(v, list): v = _decode_list(v) newdict[k] = v @@ -3844,8 +3845,8 @@ def _outbound_visitor(value): return value.strftime("%Y-%m-%dT%H:%M:%SZ") # ensure return is six.text_type - if isinstance(value, six.string_types): - return six.ensure_text(value) + if isinstance(value, str): + return sgutils.ensure_text(value) return value @@ -3865,7 +3866,7 @@ def _change_tz(x): _change_tz = None def _inbound_visitor(value): - if isinstance(value, six.string_types): + if isinstance(value, str): if len(value) == 20 and self._DATE_TIME_PATTERN.match(value): try: # strptime was not on datetime in python2.4 @@ -4266,7 +4267,7 @@ def _send_form(self, url, params): else: raise ShotgunError("Unanticipated error occurred %s" % (e)) - return six.ensure_text(result) + return sgutils.ensure_text(result) else: raise ShotgunError("Max attemps limit reached.") @@ -4339,7 +4340,7 @@ def http_request(self, request): data = request.data else: data = request.get_data() - if data is not None and not isinstance(data, six.string_types): + if data is not None and not isinstance(data, str): files = [] params = [] for key, value in data.items(): @@ -4348,7 +4349,7 @@ def http_request(self, request): else: params.append((key, value)) if not files: - data = six.ensure_binary(urllib.parse.urlencode(params, True)) # sequencing on + data = sgutils.ensure_binary(urllib.parse.urlencode(params, True)) # sequencing on else: boundary, data = self.encode(params, files) content_type = "multipart/form-data; boundary=%s" % boundary @@ -4371,15 +4372,15 @@ def encode(self, params, files, boundary=None, buffer=None): if buffer is None: buffer = BytesIO() for (key, value) in params: - if not isinstance(value, six.string_types): + if not isinstance(value, str): # If value is not a string (e.g. int) cast to text - value = six.text_type(value) - value = six.ensure_text(value) - key = six.ensure_text(key) + value = str(value) + value = sgutils.ensure_text(value) + key = sgutils.ensure_text(key) - buffer.write(six.ensure_binary("--%s\r\n" % boundary)) - buffer.write(six.ensure_binary("Content-Disposition: form-data; name=\"%s\"" % key)) - buffer.write(six.ensure_binary("\r\n\r\n%s\r\n" % value)) + buffer.write(sgutils.ensure_binary("--%s\r\n" % boundary)) + buffer.write(sgutils.ensure_binary("Content-Disposition: form-data; name=\"%s\"" % key)) + buffer.write(sgutils.ensure_binary("\r\n\r\n%s\r\n" % value)) for (key, fd) in files: # On Windows, it's possible that we were forced to open a file # with non-ascii characters as unicode. In that case, we need to @@ -4387,24 +4388,24 @@ def encode(self, params, files, boundary=None, buffer=None): # If we don't, the mix of unicode and strings going into the # buffer can cause UnicodeEncodeErrors to be raised. filename = fd.name - filename = six.ensure_text(filename) + filename = sgutils.ensure_text(filename) filename = filename.split("/")[-1] - key = six.ensure_text(key) + key = sgutils.ensure_text(key) content_type = mimetypes.guess_type(filename)[0] content_type = content_type or "application/octet-stream" file_size = os.fstat(fd.fileno())[stat.ST_SIZE] - buffer.write(six.ensure_binary("--%s\r\n" % boundary)) + buffer.write(sgutils.ensure_binary("--%s\r\n" % boundary)) c_dis = "Content-Disposition: form-data; name=\"%s\"; filename=\"%s\"%s" content_disposition = c_dis % (key, filename, "\r\n") - buffer.write(six.ensure_binary(content_disposition)) - buffer.write(six.ensure_binary("Content-Type: %s\r\n" % content_type)) - buffer.write(six.ensure_binary("Content-Length: %s\r\n" % file_size)) + buffer.write(sgutils.ensure_binary(content_disposition)) + buffer.write(sgutils.ensure_binary("Content-Type: %s\r\n" % content_type)) + buffer.write(sgutils.ensure_binary("Content-Length: %s\r\n" % file_size)) - buffer.write(six.ensure_binary("\r\n")) + buffer.write(sgutils.ensure_binary("\r\n")) fd.seek(0) shutil.copyfileobj(fd, buffer) - buffer.write(six.ensure_binary("\r\n")) - buffer.write(six.ensure_binary("--%s--\r\n\r\n" % boundary)) + buffer.write(sgutils.ensure_binary("\r\n")) + buffer.write(sgutils.ensure_binary("--%s--\r\n\r\n" % boundary)) buffer = buffer.getvalue() return boundary, buffer diff --git a/tests/base.py b/tests/base.py index 04e52aa72..4eaafb867 100644 --- a/tests/base.py +++ b/tests/base.py @@ -175,7 +175,7 @@ def _mock_http(self, data, headers=None, status=None): if not isinstance(self.sg._http_request, mock.Mock): return - if not isinstance(data, six.string_types): + if not isinstance(data, str): if six.PY2: data = json.dumps( data, @@ -208,7 +208,7 @@ def _assert_http_method(self, method, params, check_auth=True): """Asserts _http_request is called with the method and params.""" args, _ = self.sg._http_request.call_args arg_body = args[2] - assert isinstance(arg_body, six.binary_type) + assert isinstance(arg_body, bytes) arg_body = json.loads(arg_body) arg_params = arg_body.get("params") diff --git a/tests/test_client.py b/tests/test_client.py index 6275e4d28..dc3fa3ec5 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -17,7 +17,7 @@ import re from shotgun_api3.lib.six.moves import urllib -from shotgun_api3.lib import six +from shotgun_api3.lib import six, sgutils try: import simplejson as json except ImportError: @@ -44,7 +44,7 @@ def b64encode(val): - return base64encode(six.ensure_binary(val)).decode("utf-8") + return base64encode(sgutils.ensure_binary(val)).decode("utf-8") class TestShotgunClient(base.MockTestBase): @@ -424,7 +424,7 @@ def test_call_rpc(self): # Test unicode mixed with utf-8 as reported in Ticket #17959 d = {"results": ["foo", "bar"]} - a = {"utf_str": "\xe2\x88\x9a", "unicode_str": six.ensure_text("\xe2\x88\x9a")} + a = {"utf_str": "\xe2\x88\x9a", "unicode_str": sgutils.ensure_text("\xe2\x88\x9a")} self._mock_http(d) rv = self.sg._call_rpc("list", a) expected = "rpc response with list result" @@ -585,14 +585,14 @@ def _datetime(s, f): return datetime.datetime(*time.strptime(s, f)[:6]) def assert_wire(wire, match): - self.assertTrue(isinstance(wire["date"], six.string_types)) + self.assertTrue(isinstance(wire["date"], str)) d = _datetime(wire["date"], "%Y-%m-%d").date() d = wire['date'] self.assertEqual(match["date"], d) - self.assertTrue(isinstance(wire["datetime"], six.string_types)) + self.assertTrue(isinstance(wire["datetime"], str)) d = _datetime(wire["datetime"], "%Y-%m-%dT%H:%M:%SZ") self.assertEqual(match["datetime"], d) - self.assertTrue(isinstance(wire["time"], six.string_types)) + self.assertTrue(isinstance(wire["time"], str)) d = _datetime(wire["time"], "%Y-%m-%dT%H:%M:%SZ") self.assertEqual(match["time"], d.time()) @@ -621,16 +621,16 @@ def test_encode_payload(self): d = {"this is ": u"my data \u00E0"} j = self.sg._encode_payload(d) - self.assertTrue(isinstance(j, six.binary_type)) + self.assertTrue(isinstance(j, bytes)) d = { "this is ": u"my data" } j = self.sg._encode_payload(d) - self.assertTrue(isinstance(j, six.binary_type)) + self.assertTrue(isinstance(j, bytes)) def test_decode_response_ascii(self): - self._assert_decode_resonse(True, six.ensure_str(u"my data \u00E0", encoding='utf8')) + self._assert_decode_resonse(True, sgutils.ensure_str(u"my data \u00E0", encoding='utf8')) def test_decode_response_unicode(self): self._assert_decode_resonse(False, u"my data \u00E0") From aaed90e4a5c6519dcb4a4ed68d3c89fb96853aea Mon Sep 17 00:00:00 2001 From: Carlos Villavicencio <123113322+carlos-villavicencio-adsk@users.noreply.github.com> Date: Tue, 13 Aug 2024 10:15:10 -0500 Subject: [PATCH 067/125] Packaging for v3.6.2 (#354) --- HISTORY.rst | 7 +++++++ setup.py | 2 +- shotgun_api3/shotgun.py | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 3b7dd4d95..92bf444d9 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -4,6 +4,13 @@ Flow Production Tracking Python API Changelog Here you can see the full list of changes between each Python API release. +v3.6.2 (2024 Aug 13) +==================== +- Remove Ticket entity reference and prepare this to run in CI. +- Condition auth for Jenkins environment. +- Update certifi to 2024.7.4. +- FIRST PHASE Python2 removing. + v3.6.1 (2024 Jun 6) =================== - Adds multi_entity_update_modes support to mockgun ``update()`` and ``batch()`` methods. diff --git a/setup.py b/setup.py index a0f51e764..c46fff40b 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ setup( name='shotgun_api3', - version='3.6.1', + version='3.6.2', description='Flow Production Tracking Python API', long_description=readme, author='Autodesk', diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index f5c2c5209..99b2a5957 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -116,7 +116,7 @@ def _is_mimetypes_broken(): # ---------------------------------------------------------------------------- # Version -__version__ = "3.6.1" +__version__ = "3.6.2" # ---------------------------------------------------------------------------- # Errors From 319108b4b8da6a768fa89d051e4d1af5cde7638a Mon Sep 17 00:00:00 2001 From: Carlos Villavicencio <123113322+carlos-villavicencio-adsk@users.noreply.github.com> Date: Tue, 5 Nov 2024 09:55:20 -0500 Subject: [PATCH 068/125] Remove min supported py version on docs (#358) --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index eaf1e9ccc..f960e941f 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,6 @@ Autodesk provides a simple Python-based API for accessing Flow Production Tracki The latest version can always be found at http://github.com/shotgunsoftware/python-api -## Minimum Requirements - -* Python v3.7 - ## Documentation Tutorials and detailed documentation about the Python API are available at http://developer.shotgridsoftware.com/python-api). From b5fc779a3054ce9b4b2646fae7b384c957353a18 Mon Sep 17 00:00:00 2001 From: Carlos Villavicencio <123113322+carlos-villavicencio-adsk@users.noreply.github.com> Date: Thu, 7 Nov 2024 10:37:35 -0500 Subject: [PATCH 069/125] SG-36700 Upgrade Azure Pipelines vm images (#359) * Upgrade Azure Pipelines vm images * Downgrade codecov to support x86_64 architecture --- azure-pipelines-templates/run-tests.yml | 2 +- azure-pipelines.yml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/azure-pipelines-templates/run-tests.yml b/azure-pipelines-templates/run-tests.yml index 551c06989..edf5ffe4a 100644 --- a/azure-pipelines-templates/run-tests.yml +++ b/azure-pipelines-templates/run-tests.yml @@ -146,7 +146,7 @@ jobs: displayName: Uploading code coverage - ${{ else }}: - script: | - curl -Os https://uploader.codecov.io/latest/macos/codecov + curl -Os https://uploader.codecov.io/v0.7.3/macos/codecov chmod +x codecov ./codecov -f coverage.xml displayName: Uploading code coverage diff --git a/azure-pipelines.yml b/azure-pipelines.yml index cc8f6b887..989745755 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -58,14 +58,14 @@ jobs: - template: azure-pipelines-templates/run-tests.yml parameters: name: Linux - vm_image: 'ubuntu-20.04' + vm_image: 'ubuntu-latest' - template: azure-pipelines-templates/run-tests.yml parameters: name: macOS - vm_image: 'macOS-12' + vm_image: 'macOS-latest' - template: azure-pipelines-templates/run-tests.yml parameters: name: Windows - vm_image: 'windows-2022' + vm_image: 'windows-latest' From d0b5b506cb72ad70a26989cfc10608df2d8f0ace Mon Sep 17 00:00:00 2001 From: Sungbin Lee Date: Fri, 15 Nov 2024 23:22:57 +0900 Subject: [PATCH 070/125] Fix incorrect Shotgun import in Python API example (#355) - Corrected the import statement for the Shotgun class to use the proper module path 'shotgun_api3.Shotgun' --- docs/cookbook/usage_tips.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cookbook/usage_tips.rst b/docs/cookbook/usage_tips.rst index c2aa362b2..91cd6e8cb 100644 --- a/docs/cookbook/usage_tips.rst +++ b/docs/cookbook/usage_tips.rst @@ -85,7 +85,7 @@ Then when you're writing scripts, you don't need to worry about remembering whic import shotgun_api3 import studio_globals - sg = Shotgun('https://my-site.shotgrid.autodesk.com', 'script_name', '0123456789abcdef0123456789abcdef0123456') + sg = shotgun_api3.Shotgun('https://my-site.shotgrid.autodesk.com', 'script_name', '0123456789abcdef0123456789abcdef0123456') result = sg.find(studio_globals.ENTITY_WIDGET, filters=[['sg_status_list', 'is', 'ip']], fields=['code', 'sg_shot']) From 31df878d537b81fe9d118443631e91d3fe3a98ea Mon Sep 17 00:00:00 2001 From: Carlos Villavicencio <123113322+carlos-villavicencio-adsk@users.noreply.github.com> Date: Mon, 9 Dec 2024 12:52:42 -0500 Subject: [PATCH 071/125] SG-36677 Optimize payload by prevent unnecessary data (#360) * Optimize payload by prevent unnecessary data * Packaging for pre-release * Code review improvements * Improve example * Improve example * Process env var * Format documentation code * Swap env var logic * Restrict env var values * Remove lowercase transform * Read env var once * Add unit tests * Packaging for v3.7.0 --- HISTORY.rst | 7 +++++ docs/reference.rst | 30 +++++++++++++++++++++ setup.py | 11 ++------ shotgun_api3/shotgun.py | 29 +++++++++++++++++++-- tests/test_unit.py | 58 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 124 insertions(+), 11 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 92bf444d9..ccfc44e33 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -4,6 +4,13 @@ Flow Production Tracking Python API Changelog Here you can see the full list of changes between each Python API release. +v3.7.0 (2024 Dec 9) +=========================== +- Remove unnecessary data in the payload when combining related queries before sending it to the server. + This would improve overall performance decreasing network latency and server processing. + See documentation for more information. + + v3.6.2 (2024 Aug 13) ==================== - Remove Ticket entity reference and prepare this to run in CI. diff --git a/docs/reference.rst b/docs/reference.rst index da171ea85..6304fc09e 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -949,6 +949,36 @@ Stores the number of milliseconds to wait between request retries. By default, In the case that both this environment variable and the config's ``rpc_attempt_interval`` property are set, the value in ``rpc_attempt_interal`` will be used. + +SHOTGUN_API_ENABLE_ENTITY_OPTIMIZATION +======================================= + +.. note:: (v3.7.0) This is an experimental feature. Feel free to disable this feature if you are experiencing any issues. + +When set to ``1``, this environment variable will enable the entity optimization feature. +This feature is disabled by default and is used to reduce the payload size made to the server when retrieving entities +improving overall performance by decreasing network latency and server processing. + +For example, a ``find`` call like this: + +.. code-block:: python + + sg.find('Asset', [['project', 'is', { + 'created_at': datetime.datetime(2015, 12, 16, 11, 2, 10, tzinfo), + 'id': 9999, + 'name': 'Demo: Game', + 'type': 'Project', + # More entity attributes + }]]) + + +Will internally be transformed as if you invoked something like this: + +.. code-block:: python + + sg.find('Asset', [['project', 'is', {'id': 999, 'type': 'Project'}]]) + + ************ Localization ************ diff --git a/setup.py b/setup.py index c46fff40b..3305fd5af 100644 --- a/setup.py +++ b/setup.py @@ -18,16 +18,9 @@ f = open('LICENSE') license = f.read().strip() -# For python 2.4 support -script_args = sys.argv[1:] -if (sys.version_info[0] <= 2) or (sys.version_info[0] == 2 and sys.version_info[1] <= 5): - if 'install' in script_args and '--no-compile' not in script_args: - script_args.append('--no-compile') - - setup( name='shotgun_api3', - version='3.6.2', + version='3.7.0', description='Flow Production Tracking Python API', long_description=readme, author='Autodesk', @@ -35,7 +28,7 @@ url='https://github.com/shotgunsoftware/python-api', license=license, packages=find_packages(exclude=('tests',)), - script_args=script_args, + script_args=sys.argv[1:], include_package_data=True, package_data={'': ['cacerts.txt', 'cacert.pem']}, zip_safe=False, diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index 99b2a5957..87f68d3e9 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -105,6 +105,8 @@ def _is_mimetypes_broken(): SG_TIMEZONE = SgTimezone() +SHOTGUN_API_ENABLE_ENTITY_OPTIMIZATION = False + NO_SSL_VALIDATION = False """ Turns off hostname matching validation for SSL certificates @@ -116,7 +118,7 @@ def _is_mimetypes_broken(): # ---------------------------------------------------------------------------- # Version -__version__ = "3.6.2" +__version__ = "3.7.0" # ---------------------------------------------------------------------------- # Errors @@ -649,7 +651,11 @@ def __init__(self, if self.config.rpc_attempt_interval < 0: raise ValueError("Value of SHOTGUN_API_RETRY_INTERVAL must be positive, " "got '%s'." % self.config.rpc_attempt_interval) - + + global SHOTGUN_API_ENABLE_ENTITY_OPTIMIZATION + if os.environ.get("SHOTGUN_API_ENABLE_ENTITY_OPTIMIZATION", "0").strip().lower() == "1": + SHOTGUN_API_ENABLE_ENTITY_OPTIMIZATION = True + self._connection = None self.__ca_certs = self._get_certs_file(ca_certs) @@ -4470,6 +4476,25 @@ def _translate_filters_simple(sg_filter): if len(values) == 1 and isinstance(values[0], (list, tuple)): values = values[0] + # Payload optimization: Do not send a full object + # just send the `type` and `id` when combining related queries + global SHOTGUN_API_ENABLE_ENTITY_OPTIMIZATION + if ( + SHOTGUN_API_ENABLE_ENTITY_OPTIMIZATION + and condition["path"] != "id" + and condition["relation"] in ["is", "is_not"] + and isinstance(values[0], dict) + ): + try: + values = [ + { + "type": values[0]["type"], + "id": values[0]["id"], + } + ] + except KeyError: + pass + condition["values"] = values return condition diff --git a/tests/test_unit.py b/tests/test_unit.py index 84bd35b60..096ca9327 100644 --- a/tests/test_unit.py +++ b/tests/test_unit.py @@ -405,6 +405,63 @@ def test_invalid(self): self.assertRaises(api.ShotgunError, api.shotgun._translate_filters, filters, "all") + def test_related_object(self): + filters = [ + [ + "project", + "is", + {"foo": "foo", "bar": "bar", "id": 999, "baz": "baz", "type": "Anything"}, + ], + ] + expected = { + "logical_operator": "and", + "conditions": [ + { + "path": "project", + "relation": "is", + "values": [ + { + "foo": "foo", + "bar": "bar", + "baz": "baz", + "id": 999, + "type": "Anything", + } + ], + } + ], + } + result = api.shotgun._translate_filters(filters, "all") + self.assertEqual(result, expected) + + def test_related_object_entity_optimization(self): + filters = [ + [ + "project", + "is", + {"foo": "foo", "bar": "bar", "id": 999, "baz": "baz", "type": "Anything"}, + ], + ] + expected = { + "logical_operator": "and", + "conditions": [ + { + "path": "project", + "relation": "is", + "values": [ + { + "id": 999, + "type": "Anything", + } + ], + } + ], + } + os.environ["SHOTGUN_API_ENABLE_ENTITY_OPTIMIZATION"] = "1" + api.Shotgun("http://server_path", "script_name", "api_key", connect=False) + result = api.shotgun._translate_filters(filters, "all") + self.assertEqual(result, expected) + class TestCerts(unittest.TestCase): # A dummy bad url provided by Amazon @@ -506,5 +563,6 @@ def _test_mimetypes_import(self, platform, major, minor, patch_number, result, m mock.platform = platform self.assertEqual(_is_mimetypes_broken(), result) + if __name__ == '__main__': unittest.main() From f0451f5188a34f6def8a3bbc785928de2489c075 Mon Sep 17 00:00:00 2001 From: Carlos Villavicencio <123113322+carlos-villavicencio-adsk@users.noreply.github.com> Date: Tue, 28 Jan 2025 14:59:29 -0500 Subject: [PATCH 072/125] SG-37544 Include the "in" and "not_in" operators for payload optimization (#362) * Include the "in" and "not_in" for payload optimization * Use a decorator to mock environmental variable --- shotgun_api3/shotgun.py | 9 ++------- tests/test_unit.py | 40 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index 87f68d3e9..58f621323 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -4482,16 +4482,11 @@ def _translate_filters_simple(sg_filter): if ( SHOTGUN_API_ENABLE_ENTITY_OPTIMIZATION and condition["path"] != "id" - and condition["relation"] in ["is", "is_not"] + and condition["relation"] in ["is", "is_not", "in", "not_in"] and isinstance(values[0], dict) ): try: - values = [ - { - "type": values[0]["type"], - "id": values[0]["id"], - } - ] + values = [{"type": v["type"], "id": v["id"]} for v in values] except KeyError: pass diff --git a/tests/test_unit.py b/tests/test_unit.py index 096ca9327..3f2e7593d 100644 --- a/tests/test_unit.py +++ b/tests/test_unit.py @@ -12,6 +12,7 @@ import os import unittest +from unittest import mock from .mock import patch import shotgun_api3 as api from shotgun_api3.shotgun import _is_mimetypes_broken @@ -434,7 +435,8 @@ def test_related_object(self): result = api.shotgun._translate_filters(filters, "all") self.assertEqual(result, expected) - def test_related_object_entity_optimization(self): + @mock.patch.dict(os.environ, {"SHOTGUN_API_ENABLE_ENTITY_OPTIMIZATION": "1"}) + def test_related_object_entity_optimization_is(self): filters = [ [ "project", @@ -457,7 +459,41 @@ def test_related_object_entity_optimization(self): } ], } - os.environ["SHOTGUN_API_ENABLE_ENTITY_OPTIMIZATION"] = "1" + api.Shotgun("http://server_path", "script_name", "api_key", connect=False) + result = api.shotgun._translate_filters(filters, "all") + self.assertEqual(result, expected) + + @mock.patch.dict(os.environ, {"SHOTGUN_API_ENABLE_ENTITY_OPTIMIZATION": "1"}) + def test_related_object_entity_optimization_in(self): + filters = [ + [ + "project", + "in", + [ + {"foo1": "foo1", "bar1": "bar1", "id": 999, "baz1": "baz1", "type": "Anything"}, + {"foo2": "foo2", "bar2": "bar2", "id": 998, "baz2": "baz2", "type": "Anything"} + ], + ], + ] + expected = { + "logical_operator": "and", + "conditions": [ + { + "path": "project", + "relation": "in", + "values": [ + { + "id": 999, + "type": "Anything", + }, + { + "id": 998, + "type": "Anything", + } + ], + } + ], + } api.Shotgun("http://server_path", "script_name", "api_key", connect=False) result = api.shotgun._translate_filters(filters, "all") self.assertEqual(result, expected) From b83b9e27bf813ce2048aa0a41e8707a91ff54a00 Mon Sep 17 00:00:00 2001 From: Carlos Villavicencio <123113322+carlos-villavicencio-adsk@users.noreply.github.com> Date: Fri, 7 Feb 2025 09:10:11 -0500 Subject: [PATCH 073/125] SG-37548 Add payload optimization on update method (#363) * Include the "in" and "not_in" for payload optimization * Use a decorator to mock environmental variable * Add payload optimization for update action * Change conditional order * Extract function to be reused. Improve testing. * Fix typo * Fix test * Support multi entity, Add tests * Apply CR feedback * Update shotgun_api3/shotgun.py Co-authored-by: Julien Langlois <16244608+julien-lang@users.noreply.github.com> --------- Co-authored-by: Julien Langlois <16244608+julien-lang@users.noreply.github.com> --- shotgun_api3/shotgun.py | 52 ++++++++++---- tests/test_unit.py | 149 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 188 insertions(+), 13 deletions(-) diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index 58f621323..d3f2cfba1 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -1132,6 +1132,28 @@ def _add_project_param(self, params, project_entity): params["project"] = project_entity return params + + def _translate_update_params( + self, entity_type, entity_id, data, multi_entity_update_modes + ): + global SHOTGUN_API_ENABLE_ENTITY_OPTIMIZATION + + def optimize_field(field_dict): + if SHOTGUN_API_ENABLE_ENTITY_OPTIMIZATION: + return {k: _get_type_and_id_from_value(v) for k, v in field_dict.items()} + return field_dict + + full_fields = self._dict_to_list( + data, + extra_data=self._dict_to_extra_data( + multi_entity_update_modes, "multi_entity_update_mode" + ), + ) + return { + "type": entity_type, + "id": entity_id, + "fields": [optimize_field(field_dict) for field_dict in full_fields], + } def summarize(self, entity_type, @@ -1463,14 +1485,7 @@ def update(self, entity_type, entity_id, data, multi_entity_update_modes=None): upload_filmstrip_image = data.pop("filmstrip_image") if data: - params = { - "type": entity_type, - "id": entity_id, - "fields": self._dict_to_list( - data, - extra_data=self._dict_to_extra_data( - multi_entity_update_modes, "multi_entity_update_mode")) - } + params = self._translate_update_params(entity_type, entity_id, data, multi_entity_update_modes) record = self._call_rpc("update", params) result = self._parse_records(record)[0] else: @@ -4485,10 +4500,7 @@ def _translate_filters_simple(sg_filter): and condition["relation"] in ["is", "is_not", "in", "not_in"] and isinstance(values[0], dict) ): - try: - values = [{"type": v["type"], "id": v["id"]} for v in values] - except KeyError: - pass + values = [_get_type_and_id_from_value(v) for v in values] condition["values"] = values @@ -4500,3 +4512,19 @@ def _version_str(version): Convert a tuple of int's to a '.' separated str. """ return ".".join(map(str, version)) + + +def _get_type_and_id_from_value(value): + """ + For an entity dictionary, returns a new dictionary with only the type and id keys. + If any of these keys are not present, the original dictionary is returned. + """ + try: + if isinstance(value, dict): + return {"type": value["type"], "id": value["id"]} + elif isinstance(value, list): + return [{"type": v["type"], "id": v["id"]} for v in value] + except (KeyError, TypeError): + LOG.debug(f"Could not optimize entity value {value}") + + return value diff --git a/tests/test_unit.py b/tests/test_unit.py index 3f2e7593d..c8144d51b 100644 --- a/tests/test_unit.py +++ b/tests/test_unit.py @@ -290,6 +290,8 @@ def test_py_version(self, mock_sys): class TestFilters(unittest.TestCase): + maxDiff = None + def test_empty(self): expected = { "logical_operator": "and", @@ -463,6 +465,28 @@ def test_related_object_entity_optimization_is(self): result = api.shotgun._translate_filters(filters, "all") self.assertEqual(result, expected) + # Now test a non-related object. The expected result should not be optimized. + filters = [ + [ + "something", + "is", + {"foo": "foo", "bar": "bar"}, + ], + ] + expected = { + "logical_operator": "and", + "conditions": [ + { + "path": "something", + "relation": "is", + "values": [{'bar': 'bar', 'foo': 'foo'}], + } + ], + } + api.Shotgun("http://server_path", "script_name", "api_key", connect=False) + result = api.shotgun._translate_filters(filters, "all") + self.assertEqual(result, expected) + @mock.patch.dict(os.environ, {"SHOTGUN_API_ENABLE_ENTITY_OPTIMIZATION": "1"}) def test_related_object_entity_optimization_in(self): filters = [ @@ -471,7 +495,8 @@ def test_related_object_entity_optimization_in(self): "in", [ {"foo1": "foo1", "bar1": "bar1", "id": 999, "baz1": "baz1", "type": "Anything"}, - {"foo2": "foo2", "bar2": "bar2", "id": 998, "baz2": "baz2", "type": "Anything"} + {"foo2": "foo2", "bar2": "bar2", "id": 998, "baz2": "baz2", "type": "Anything"}, + {"foo3": "foo3", "bar3": "bar3"}, ], ], ] @@ -489,6 +514,10 @@ def test_related_object_entity_optimization_in(self): { "id": 998, "type": "Anything", + }, + { + "foo3": "foo3", + "bar3": "bar3", } ], } @@ -498,6 +527,124 @@ def test_related_object_entity_optimization_in(self): result = api.shotgun._translate_filters(filters, "all") self.assertEqual(result, expected) + def test_related_object_update_entity(self): + entity_type = "Anything" + entity_id = 999 + multi_entity_update_modes = {"link": "set", "name": "set"} + data = { + "name": "test", + "link": { + "name": "test", + "url": "http://test.com", + }, + } + expected = { + "id": 999, + "type": "Anything", + "fields": [ + { + "field_name": "name", + "value": "test", + "multi_entity_update_mode": "set", + }, + { + "field_name": "link", + "value": { + "name": "test", + "url": "http://test.com", + }, + "multi_entity_update_mode": "set", + }, + ], + } + sg = api.Shotgun("http://server_path", "script_name", "api_key", connect=False) + result = sg._translate_update_params(entity_type, entity_id, data, multi_entity_update_modes) + self.assertEqual(result, expected) + + @mock.patch.dict(os.environ, {"SHOTGUN_API_ENABLE_ENTITY_OPTIMIZATION": "1"}) + def test_related_object_update_optimization_entity(self): + entity_type = "Anything" + entity_id = 999 + multi_entity_update_modes = {"project": "set", "link": "set", "name": "set"} + data = { + "name": "test", + "link": { + "name": "test", + "url": "http://test.com", + }, + "project": { + "foo1": "foo1", + "bar1": "bar1", + "id": 888, + "baz1": "baz1", + "type": "Project", + }, + } + expected = { + "id": 999, + "type": "Anything", + "fields": [ + { + "field_name": "name", + "value": "test", + "multi_entity_update_mode": "set", + }, + { + "field_name": "link", + "value": { + "name": "test", + "url": "http://test.com", + }, + "multi_entity_update_mode": "set", + }, + { + "field_name": "project", + "multi_entity_update_mode": "set", + "value": { + # Entity is optimized with type/id fields. + "id": 888, + "type": "Project", + }, + }, + ], + } + sg = api.Shotgun("http://server_path", "script_name", "api_key", connect=False) + result = sg._translate_update_params(entity_type, entity_id, data, multi_entity_update_modes) + self.assertEqual(result, expected) + + @mock.patch.dict(os.environ, {"SHOTGUN_API_ENABLE_ENTITY_OPTIMIZATION": "1"}) + def test_related_object_update_optimization_entity_multi(self): + entity_type = "Asset" + entity_id = 6626 + data = { + "sg_status_list": "ip", + "project": {"id": 70, "type": "Project", "name": "disposable name 70"}, + "sg_vvv": [ + {"id": 6441, "type": "Asset", "name": "disposable name 6441"}, + {"id": 6440, "type": "Asset"}, + ], + "sg_class": {"id": 1, "type": "CustomEntity53", "name": "disposable name 1"}, + } + expected = { + "type": "Asset", + "id": 6626, + "fields": [ + {"field_name": "sg_status_list", "value": "ip"}, + {"field_name": "project", "value": {"type": "Project", "id": 70}}, + { + "field_name": "sg_vvv", + "value": [ + {"id": 6441, "type": "Asset"}, + {"id": 6440, "type": "Asset"}, + ], + }, + {"field_name": "sg_class", "value": {"type": "CustomEntity53", "id": 1}}, + ], + } + sg = api.Shotgun("http://server_path", "script_name", "api_key", connect=False) + result = sg._translate_update_params(entity_type, entity_id, data, None) + self.assertEqual(result, expected) + class TestCerts(unittest.TestCase): # A dummy bad url provided by Amazon From 9b9e5380519637c5626ab679f7f5f5e22f26476b Mon Sep 17 00:00:00 2001 From: Carlos Villavicencio <123113322+carlos-villavicencio-adsk@users.noreply.github.com> Date: Wed, 12 Feb 2025 08:40:54 -0500 Subject: [PATCH 074/125] SG-38119 Release v3.8.0. Make payload optimization default (#366) * Make payload optimization default * Packaging for v3.8.0 * Update HISTORY.rst Co-authored-by: Julien Langlois <16244608+julien-lang@users.noreply.github.com> --------- Co-authored-by: Julien Langlois <16244608+julien-lang@users.noreply.github.com> --- HISTORY.rst | 11 ++++++++++- docs/reference.rst | 6 +++--- setup.py | 2 +- shotgun_api3/shotgun.py | 22 +++++++++++----------- tests/test_unit.py | 10 ++++++---- 5 files changed, 31 insertions(+), 20 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index ccfc44e33..82309527e 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -4,8 +4,17 @@ Flow Production Tracking Python API Changelog Here you can see the full list of changes between each Python API release. +v3.8.0 (2024 Feb 7) +=================== + +- Extend the payload optimizations to the ``in`` and ``not_in`` filters and + the ``update`` method. +- The payload optimization is now enabled by default. + It can be disabled with the ``SHOTGUN_API_DISABLE_ENTITY_OPTIMIZATION`` + environment variable. + v3.7.0 (2024 Dec 9) -=========================== +=================== - Remove unnecessary data in the payload when combining related queries before sending it to the server. This would improve overall performance decreasing network latency and server processing. See documentation for more information. diff --git a/docs/reference.rst b/docs/reference.rst index 6304fc09e..b3260b3be 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -950,13 +950,13 @@ Stores the number of milliseconds to wait between request retries. By default, In the case that both this environment variable and the config's ``rpc_attempt_interval`` property are set, the value in ``rpc_attempt_interal`` will be used. -SHOTGUN_API_ENABLE_ENTITY_OPTIMIZATION +SHOTGUN_API_DISABLE_ENTITY_OPTIMIZATION ======================================= .. note:: (v3.7.0) This is an experimental feature. Feel free to disable this feature if you are experiencing any issues. -When set to ``1``, this environment variable will enable the entity optimization feature. -This feature is disabled by default and is used to reduce the payload size made to the server when retrieving entities +When set to ``1``, this environment variable will disable the entity optimization feature. +This feature is enabled by default and is used to reduce the payload size made to the server when retrieving entities improving overall performance by decreasing network latency and server processing. For example, a ``find`` call like this: diff --git a/setup.py b/setup.py index 3305fd5af..337e3b13b 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ setup( name='shotgun_api3', - version='3.7.0', + version='3.8.0', description='Flow Production Tracking Python API', long_description=readme, author='Autodesk', diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index d3f2cfba1..f0d4faf48 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -105,7 +105,7 @@ def _is_mimetypes_broken(): SG_TIMEZONE = SgTimezone() -SHOTGUN_API_ENABLE_ENTITY_OPTIMIZATION = False +SHOTGUN_API_DISABLE_ENTITY_OPTIMIZATION = False NO_SSL_VALIDATION = False """ @@ -118,7 +118,7 @@ def _is_mimetypes_broken(): # ---------------------------------------------------------------------------- # Version -__version__ = "3.7.0" +__version__ = "3.8.0" # ---------------------------------------------------------------------------- # Errors @@ -652,9 +652,9 @@ def __init__(self, raise ValueError("Value of SHOTGUN_API_RETRY_INTERVAL must be positive, " "got '%s'." % self.config.rpc_attempt_interval) - global SHOTGUN_API_ENABLE_ENTITY_OPTIMIZATION - if os.environ.get("SHOTGUN_API_ENABLE_ENTITY_OPTIMIZATION", "0").strip().lower() == "1": - SHOTGUN_API_ENABLE_ENTITY_OPTIMIZATION = True + global SHOTGUN_API_DISABLE_ENTITY_OPTIMIZATION + if os.environ.get("SHOTGUN_API_DISABLE_ENTITY_OPTIMIZATION", "0").strip().lower() == "1": + SHOTGUN_API_DISABLE_ENTITY_OPTIMIZATION = True self._connection = None @@ -1136,12 +1136,12 @@ def _add_project_param(self, params, project_entity): def _translate_update_params( self, entity_type, entity_id, data, multi_entity_update_modes ): - global SHOTGUN_API_ENABLE_ENTITY_OPTIMIZATION + global SHOTGUN_API_DISABLE_ENTITY_OPTIMIZATION def optimize_field(field_dict): - if SHOTGUN_API_ENABLE_ENTITY_OPTIMIZATION: - return {k: _get_type_and_id_from_value(v) for k, v in field_dict.items()} - return field_dict + if SHOTGUN_API_DISABLE_ENTITY_OPTIMIZATION: + return field_dict + return {k: _get_type_and_id_from_value(v) for k, v in field_dict.items()} full_fields = self._dict_to_list( data, @@ -4493,9 +4493,9 @@ def _translate_filters_simple(sg_filter): # Payload optimization: Do not send a full object # just send the `type` and `id` when combining related queries - global SHOTGUN_API_ENABLE_ENTITY_OPTIMIZATION + global SHOTGUN_API_DISABLE_ENTITY_OPTIMIZATION if ( - SHOTGUN_API_ENABLE_ENTITY_OPTIMIZATION + not SHOTGUN_API_DISABLE_ENTITY_OPTIMIZATION and condition["path"] != "id" and condition["relation"] in ["is", "is_not", "in", "not_in"] and isinstance(values[0], dict) diff --git a/tests/test_unit.py b/tests/test_unit.py index c8144d51b..84304cab7 100644 --- a/tests/test_unit.py +++ b/tests/test_unit.py @@ -408,6 +408,7 @@ def test_invalid(self): self.assertRaises(api.ShotgunError, api.shotgun._translate_filters, filters, "all") + @mock.patch.dict(os.environ, {"SHOTGUN_API_DISABLE_ENTITY_OPTIMIZATION": "1"}) def test_related_object(self): filters = [ [ @@ -434,10 +435,11 @@ def test_related_object(self): } ], } + api.Shotgun("http://server_path", "script_name", "api_key", connect=False) result = api.shotgun._translate_filters(filters, "all") self.assertEqual(result, expected) - @mock.patch.dict(os.environ, {"SHOTGUN_API_ENABLE_ENTITY_OPTIMIZATION": "1"}) + @mock.patch("shotgun_api3.shotgun.SHOTGUN_API_DISABLE_ENTITY_OPTIMIZATION", False) def test_related_object_entity_optimization_is(self): filters = [ [ @@ -487,7 +489,7 @@ def test_related_object_entity_optimization_is(self): result = api.shotgun._translate_filters(filters, "all") self.assertEqual(result, expected) - @mock.patch.dict(os.environ, {"SHOTGUN_API_ENABLE_ENTITY_OPTIMIZATION": "1"}) + @mock.patch("shotgun_api3.shotgun.SHOTGUN_API_DISABLE_ENTITY_OPTIMIZATION", False) def test_related_object_entity_optimization_in(self): filters = [ [ @@ -561,7 +563,7 @@ def test_related_object_update_entity(self): result = sg._translate_update_params(entity_type, entity_id, data, multi_entity_update_modes) self.assertEqual(result, expected) - @mock.patch.dict(os.environ, {"SHOTGUN_API_ENABLE_ENTITY_OPTIMIZATION": "1"}) + @mock.patch("shotgun_api3.shotgun.SHOTGUN_API_DISABLE_ENTITY_OPTIMIZATION", False) def test_related_object_update_optimization_entity(self): entity_type = "Anything" entity_id = 999 @@ -612,7 +614,7 @@ def test_related_object_update_optimization_entity(self): result = sg._translate_update_params(entity_type, entity_id, data, multi_entity_update_modes) self.assertEqual(result, expected) - @mock.patch.dict(os.environ, {"SHOTGUN_API_ENABLE_ENTITY_OPTIMIZATION": "1"}) + @mock.patch("shotgun_api3.shotgun.SHOTGUN_API_DISABLE_ENTITY_OPTIMIZATION", False) def test_related_object_update_optimization_entity_multi(self): entity_type = "Asset" entity_id = 6626 From b6247a39876c4d1c25ce170666adb1ef350f7e4b Mon Sep 17 00:00:00 2001 From: Eduardo Chauca <166560435+eduardoChaucaGallegos@users.noreply.github.com> Date: Tue, 18 Feb 2025 01:39:40 -0500 Subject: [PATCH 075/125] upgrade certifi to 2024.12.14 (#365) --- shotgun_api3/lib/certifi/__init__.py | 2 +- shotgun_api3/lib/certifi/cacert.pem | 284 ++++++++++++++++----------- 2 files changed, 172 insertions(+), 114 deletions(-) diff --git a/shotgun_api3/lib/certifi/__init__.py b/shotgun_api3/lib/certifi/__init__.py index d321f1bc3..ee8686bec 100644 --- a/shotgun_api3/lib/certifi/__init__.py +++ b/shotgun_api3/lib/certifi/__init__.py @@ -1,4 +1,4 @@ from .core import contents, where __all__ = ["contents", "where"] -__version__ = "2024.07.04" +__version__ = "2024.12.14" diff --git a/shotgun_api3/lib/certifi/cacert.pem b/shotgun_api3/lib/certifi/cacert.pem index a6581589b..ef509f865 100644 --- a/shotgun_api3/lib/certifi/cacert.pem +++ b/shotgun_api3/lib/certifi/cacert.pem @@ -763,35 +763,6 @@ uLjbvrW5KfnaNwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2 XjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= -----END CERTIFICATE----- -# Issuer: CN=SecureSign RootCA11 O=Japan Certification Services, Inc. -# Subject: CN=SecureSign RootCA11 O=Japan Certification Services, Inc. -# Label: "SecureSign RootCA11" -# Serial: 1 -# MD5 Fingerprint: b7:52:74:e2:92:b4:80:93:f2:75:e4:cc:d7:f2:ea:26 -# SHA1 Fingerprint: 3b:c4:9f:48:f8:f3:73:a0:9c:1e:bd:f8:5b:b1:c3:65:c7:d8:11:b3 -# SHA256 Fingerprint: bf:0f:ee:fb:9e:3a:58:1a:d5:f9:e9:db:75:89:98:57:43:d2:61:08:5c:4d:31:4f:6f:5d:72:59:aa:42:16:12 ------BEGIN CERTIFICATE----- -MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDEr -MCkGA1UEChMiSmFwYW4gQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcywgSW5jLjEcMBoG -A1UEAxMTU2VjdXJlU2lnbiBSb290Q0ExMTAeFw0wOTA0MDgwNDU2NDdaFw0yOTA0 -MDgwNDU2NDdaMFgxCzAJBgNVBAYTAkpQMSswKQYDVQQKEyJKYXBhbiBDZXJ0aWZp -Y2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1cmVTaWduIFJvb3RD -QTExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/XeqpRyQBTvLTJsz -i1oURaTnkBbR31fSIRCkF/3frNYfp+TbfPfs37gD2pRY/V1yfIw/XwFndBWW4wI8 -h9uuywGOwvNmxoVF9ALGOrVisq/6nL+k5tSAMJjzDbaTj6nU2DbysPyKyiyhFTOV -MdrAG/LuYpmGYz+/3ZMqg6h2uRMft85OQoWPIucuGvKVCbIFtUROd6EgvanyTgp9 -UK31BQ1FT0Zx/Sg+U/sE2C3XZR1KG/rPO7AxmjVuyIsG0wCR8pQIZUyxNAYAeoni -8McDWc/V1uinMrPmmECGxc0nEovMe863ETxiYAcjPitAbpSACW22s293bzUIUPsC -h8U+iQIDAQABo0IwQDAdBgNVHQ4EFgQUW/hNT7KlhtQ60vFjmqC+CfZXt94wDgYD -VR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEB -AKChOBZmLqdWHyGcBvod7bkixTgm2E5P7KN/ed5GIaGHd48HCJqypMWvDzKYC3xm -KbabfSVSSUOrTC4rbnpwrxYO4wJs+0LmGJ1F2FXI6Dvd5+H0LgscNFxsWEr7jIhQ -X5Ucv+2rIrVls4W6ng+4reV6G4pQOh29Dbx7VFALuUKvVaAYga1lme++5Jy/xIWr -QbJUb9wlze144o4MjQlJ3WN7WmmWAiGovVJZ6X01y8hSyn+B/tlr0/cR7SXf+Of5 -pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061lgeLKBObjBmN -QSdJQO7e5iNEOdyhIta6A/I= ------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" @@ -3100,50 +3071,6 @@ LJstxabArahH9CdMOA0uG0k7UvToiIMrVCjU8jVStDKDYmlkDJGcn5fqdBb9HxEG mpv0 -----END CERTIFICATE----- -# Issuer: CN=Entrust Root Certification Authority - G4 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2015 Entrust, Inc. - for authorized use only -# Subject: CN=Entrust Root Certification Authority - G4 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2015 Entrust, Inc. - for authorized use only -# Label: "Entrust Root Certification Authority - G4" -# Serial: 289383649854506086828220374796556676440 -# MD5 Fingerprint: 89:53:f1:83:23:b7:7c:8e:05:f1:8c:71:38:4e:1f:88 -# SHA1 Fingerprint: 14:88:4e:86:26:37:b0:26:af:59:62:5c:40:77:ec:35:29:ba:96:01 -# SHA256 Fingerprint: db:35:17:d1:f6:73:2a:2d:5a:b9:7c:53:3e:c7:07:79:ee:32:70:a6:2f:b4:ac:42:38:37:24:60:e6:f0:1e:88 ------BEGIN CERTIFICATE----- -MIIGSzCCBDOgAwIBAgIRANm1Q3+vqTkPAAAAAFVlrVgwDQYJKoZIhvcNAQELBQAw -gb4xCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQL -Ex9TZWUgd3d3LmVudHJ1c3QubmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykg -MjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMjAw -BgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEc0 -MB4XDTE1MDUyNzExMTExNloXDTM3MTIyNzExNDExNlowgb4xCzAJBgNVBAYTAlVT -MRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1 -c3QubmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJ -bmMuIC0gZm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3Qg -Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEc0MIICIjANBgkqhkiG9w0B -AQEFAAOCAg8AMIICCgKCAgEAsewsQu7i0TD/pZJH4i3DumSXbcr3DbVZwbPLqGgZ -2K+EbTBwXX7zLtJTmeH+H17ZSK9dE43b/2MzTdMAArzE+NEGCJR5WIoV3imz/f3E -T+iq4qA7ec2/a0My3dl0ELn39GjUu9CH1apLiipvKgS1sqbHoHrmSKvS0VnM1n4j -5pds8ELl3FFLFUHtSUrJ3hCX1nbB76W1NhSXNdh4IjVS70O92yfbYVaCNNzLiGAM -C1rlLAHGVK/XqsEQe9IFWrhAnoanw5CGAlZSCXqc0ieCU0plUmr1POeo8pyvi73T -DtTUXm6Hnmo9RR3RXRv06QqsYJn7ibT/mCzPfB3pAqoEmh643IhuJbNsZvc8kPNX -wbMv9W3y+8qh+CmdRouzavbmZwe+LGcKKh9asj5XxNMhIWNlUpEbsZmOeX7m640A -2Vqq6nPopIICR5b+W45UYaPrL0swsIsjdXJ8ITzI9vF01Bx7owVV7rtNOzK+mndm -nqxpkCIHH2E6lr7lmk/MBTwoWdPBDFSoWWG9yHJM6Nyfh3+9nEg2XpWjDrk4JFX8 -dWbrAuMINClKxuMrLzOg2qOGpRKX/YAr2hRC45K9PvJdXmd0LhyIRyk0X+IyqJwl -N4y6mACXi0mWHv0liqzc2thddG5msP9E36EYxr5ILzeUePiVSj9/E15dWf10hkNj -c0kCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD -VR0OBBYEFJ84xFYjwznooHFs6FRM5Og6sb9nMA0GCSqGSIb3DQEBCwUAA4ICAQAS -5UKme4sPDORGpbZgQIeMJX6tuGguW8ZAdjwD+MlZ9POrYs4QjbRaZIxowLByQzTS -Gwv2LFPSypBLhmb8qoMi9IsabyZIrHZ3CL/FmFz0Jomee8O5ZDIBf9PD3Vht7LGr -hFV0d4QEJ1JrhkzO3bll/9bGXp+aEJlLdWr+aumXIOTkdnrG0CSqkM0gkLpHZPt/ -B7NTeLUKYvJzQ85BK4FqLoUWlFPUa19yIqtRLULVAJyZv967lDtX/Zr1hstWO1uI -AeV8KEsD+UmDfLJ/fOPtjqF/YFOOVZ1QNBIPt5d7bIdKROf1beyAN/BYGW5KaHbw -H5Lk6rWS02FREAutp9lfx1/cH6NcjKF+m7ee01ZvZl4HliDtC3T7Zk6LERXpgUl+ -b7DUUH8i119lAg2m9IUe2K4GS0qn0jFmwvjO5QimpAKWRGhXxNUzzxkvFMSUHHuk -2fCfDrGA4tGeEWSpiBE6doLlYsKA2KSD7ZPvfC+QsDJMlhVoSFLUmQjAJOgc47Ol -IQ6SwJAfzyBfyjs4x7dtOvPmRLgOMWuIjnDrnBdSqEGULoe256YSxXXfW8AKbnuk -5F6G+TaU33fD6Q3AOfF5u0aOq0NZJ7cguyPpVkAh7DE9ZapD8j3fcEThuk0mEDuY -n/PIjhs4ViFqUZPTkcpG2om3PVODLAgfi49T3f+sHw== ------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" @@ -3485,6 +3412,46 @@ DgQWBBQxCpCPtsad0kRLgLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ +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" @@ -4214,46 +4181,6 @@ ut6Dacpps6kFtZaSF4fC0urQe87YQVt8rgIwRt7qy12a7DLCZRawTDBcMPPaTnOG BtjOiQRINzf43TNRnXCve1XYAS59BWQOhriR -----END CERTIFICATE----- -# Issuer: CN=Security Communication RootCA3 O=SECOM Trust Systems CO.,LTD. -# Subject: CN=Security Communication RootCA3 O=SECOM Trust Systems CO.,LTD. -# Label: "Security Communication RootCA3" -# Serial: 16247922307909811815 -# MD5 Fingerprint: 1c:9a:16:ff:9e:5c:e0:4d:8a:14:01:f4:35:5d:29:26 -# SHA1 Fingerprint: c3:03:c8:22:74:92:e5:61:a2:9c:5f:79:91:2b:1e:44:13:91:30:3a -# SHA256 Fingerprint: 24:a5:5c:2a:b0:51:44:2d:06:17:76:65:41:23:9a:4a:d0:32:d7:c5:51:75:aa:34:ff:de:2f:bc:4f:5c:52:94 ------BEGIN CERTIFICATE----- -MIIFfzCCA2egAwIBAgIJAOF8N0D9G/5nMA0GCSqGSIb3DQEBDAUAMF0xCzAJBgNV -BAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScw -JQYDVQQDEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTMwHhcNMTYwNjE2 -MDYxNzE2WhcNMzgwMTE4MDYxNzE2WjBdMQswCQYDVQQGEwJKUDElMCMGA1UEChMc -U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UEAxMeU2VjdXJpdHkg -Q29tbXVuaWNhdGlvbiBSb290Q0EzMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC -CgKCAgEA48lySfcw3gl8qUCBWNO0Ot26YQ+TUG5pPDXC7ltzkBtnTCHsXzW7OT4r -CmDvu20rhvtxosis5FaU+cmvsXLUIKx00rgVrVH+hXShuRD+BYD5UpOzQD11EKzA -lrenfna84xtSGc4RHwsENPXY9Wk8d/Nk9A2qhd7gCVAEF5aEt8iKvE1y/By7z/MG -TfmfZPd+pmaGNXHIEYBMwXFAWB6+oHP2/D5Q4eAvJj1+XCO1eXDe+uDRpdYMQXF7 -9+qMHIjH7Iv10S9VlkZ8WjtYO/u62C21Jdp6Ts9EriGmnpjKIG58u4iFW/vAEGK7 -8vknR+/RiTlDxN/e4UG/VHMgly1s2vPUB6PmudhvrvyMGS7TZ2crldtYXLVqAvO4 -g160a75BflcJdURQVc1aEWEhCmHCqYj9E7wtiS/NYeCVvsq1e+F7NGcLH7YMx3we -GVPKp7FKFSBWFHA9K4IsD50VHUeAR/94mQ4xr28+j+2GaR57GIgUssL8gjMunEst -+3A7caoreyYn8xrC3PsXuKHqy6C0rtOUfnrQq8PsOC0RLoi/1D+tEjtCrI8Cbn3M -0V9hvqG8OmpI6iZVIhZdXw3/JzOfGAN0iltSIEdrRU0id4xVJ/CvHozJgyJUt5rQ -T9nO/NkuHJYosQLTA70lUhw0Zk8jq/R3gpYd0VcwCBEF/VfR2ccCAwEAAaNCMEAw -HQYDVR0OBBYEFGQUfPxYchamCik0FW8qy7z8r6irMA4GA1UdDwEB/wQEAwIBBjAP -BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBDAUAA4ICAQDcAiMI4u8hOscNtybS -YpOnpSNyByCCYN8Y11StaSWSntkUz5m5UoHPrmyKO1o5yGwBQ8IibQLwYs1OY0PA -FNr0Y/Dq9HHuTofjcan0yVflLl8cebsjqodEV+m9NU1Bu0soo5iyG9kLFwfl9+qd -9XbXv8S2gVj/yP9kaWJ5rW4OH3/uHWnlt3Jxs/6lATWUVCvAUm2PVcTJ0rjLyjQI -UYWg9by0F1jqClx6vWPGOi//lkkZhOpn2ASxYfQAW0q3nHE3GYV5v4GwxxMOdnE+ -OoAGrgYWp421wsTL/0ClXI2lyTrtcoHKXJg80jQDdwj98ClZXSEIx2C/pHF7uNke -gr4Jr2VvKKu/S7XuPghHJ6APbw+LP6yVGPO5DtxnVW5inkYO0QR4ynKudtml+LLf -iAlhi+8kTtFZP1rUPcmTPCtk9YENFpb3ksP+MW/oKjJ0DvRMmEoYDjBU1cXrvMUV -nuiZIesnKwkK2/HmcBhWuwzkvvnoEKQTkrgc4NtnHVMDpCKn3F2SEDzq//wbEBrD -2NCcnWXL0CsnMQMeNuE9dnUM/0Umud1RvCPHX9jYhxBAEg09ODfnRDwYwFMJZI// -1ZqmfHAuc1Uh6N//g7kdPjIe1qZ9LPFm6Vwdp6POXiUyK+OVrCoHzrQoeIY8Laad -TdJ0MN1kURXbg4NR16/9M51NZg== ------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" @@ -4796,3 +4723,134 @@ 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----- \ No newline at end of file From 2cd8b387a165fb2773cc2d6ef4ea224dc6d581f8 Mon Sep 17 00:00:00 2001 From: Julien Langlois <16244608+julien-lang@users.noreply.github.com> Date: Wed, 19 Feb 2025 08:28:32 -0800 Subject: [PATCH 076/125] SG-33057 App pre-commit configuration and CI (#367) * Add pre-commit config * Apply pre-commit reformat * fixup! Add pre-commit config --- .flake8 | 2 +- .gitignore | 1 - .pre-commit-config.yaml | 49 + HISTORY.rst | 74 +- LICENSE | 8 +- SECURITY.md | 2 +- .../code_style_validation.yml | 50 + azure-pipelines-templates/run-tests.yml | 6 +- azure-pipelines.yml | 1 + docs/advanced/iron_python.rst | 2 +- docs/advanced/packaging.rst | 4 +- docs/authentication.rst | 1 - docs/changelog.rst | 2 +- docs/cookbook.rst | 10 +- docs/cookbook/examples/ami_handler.rst | 8 +- .../examples/ami_version_packager.rst | 69 +- docs/cookbook/examples/basic_create_shot.rst | 25 +- .../basic_create_shot_task_template.rst | 12 +- .../basic_create_version_link_shot.rst | 36 +- docs/cookbook/examples/basic_delete_shot.rst | 11 +- docs/cookbook/examples/basic_find_shot.rst | 18 +- docs/cookbook/examples/basic_sg_instance.rst | 6 +- docs/cookbook/examples/basic_update_shot.rst | 27 +- .../basic_upload_thumbnail_version.rst | 10 +- docs/cookbook/examples/svn_integration.rst | 70 +- docs/cookbook/smart_cut_fields.rst | 8 +- docs/cookbook/tasks.rst | 4 +- docs/cookbook/tasks/split_tasks.rst | 41 +- docs/cookbook/tasks/task_dependencies.rst | 76 +- docs/cookbook/tasks/updating_tasks.rst | 34 +- docs/cookbook/tutorials.rst | 2 +- docs/cookbook/usage_tips.rst | 56 +- docs/index.rst | 1 - docs/reference.rst | 66 +- nose.cfg | 2 +- run-tests | 2 + setup.py | 20 +- shotgun_api3/__init__.py | 21 +- shotgun_api3/shotgun.py | 1327 +++++---- tests/base.py | 351 +-- tests/ci_requirements.txt | 6 +- tests/mock.py | 311 ++- tests/test_api.py | 2373 ++++++++++------- tests/test_client.py | 192 +- tests/test_config_file | 2 +- tests/test_mockgun.py | 260 +- tests/test_proxy.py | 12 +- tests/test_unit.py | 401 +-- update_httplib2.py | 46 +- 49 files changed, 3617 insertions(+), 2501 deletions(-) create mode 100644 .pre-commit-config.yaml create mode 100644 azure-pipelines-templates/code_style_validation.yml diff --git a/.flake8 b/.flake8 index 02cff1e84..343f01039 100644 --- a/.flake8 +++ b/.flake8 @@ -10,4 +10,4 @@ [flake8] max-line-length = 120 -exclude = shotgun_api3/lib/httplib2/*,shotgun_api3/lib/six.py,tests/httplib2test.py,tests/mock.py \ No newline at end of file +exclude = shotgun_api3/lib/httplib2/*,shotgun_api3/lib/six.py,tests/httplib2test.py,tests/mock.py diff --git a/.gitignore b/.gitignore index 3e6ff329a..02018058b 100644 --- a/.gitignore +++ b/.gitignore @@ -33,4 +33,3 @@ build dist shotgun_api3.egg-info /%1 - diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..537da22cf --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,49 @@ +# Copyright (c) 2024, Shotgun Software Inc. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# - Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# - Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# - Neither the name of the Shotgun Software Inc nor the names of its +# contributors may be used to endorse or promote products derived from this +# software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# Styles the code properly + +# Exclude Third Pary components +exclude: "shotgun_api3/lib/.*" + +# List of super useful formatters. +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: check-ast + - id: check-case-conflict + - id: check-executables-have-shebangs + - id: check-merge-conflict + - id: end-of-file-fixer + - id: requirements-txt-fixer + - id: trailing-whitespace + + - repo: https://github.com/psf/black + rev: 25.1.0 + hooks: + - id: black diff --git a/HISTORY.rst b/HISTORY.rst index 82309527e..9858f61c8 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -9,7 +9,7 @@ v3.8.0 (2024 Feb 7) - Extend the payload optimizations to the ``in`` and ``not_in`` filters and the ``update`` method. -- The payload optimization is now enabled by default. +- The payload optimization is now enabled by default. It can be disabled with the ``SHOTGUN_API_DISABLE_ENTITY_OPTIMIZATION`` environment variable. @@ -57,7 +57,7 @@ v3.4.2 (2024 Feb 6) v3.4.1 (2024 Jan 29) ==================== - Flaky Tests -- Documentation: Fix issue regarding "in" filter prototype +- Documentation: Fix issue regarding "in" filter prototype - Documentation: Travis badge image is no working anymore - Documentation: Add ``user_subscription_read`` and ``user_subscription_create`` methods - Update Python Certifi license block @@ -208,7 +208,7 @@ v3.0.34 (2017 September 18) v3.0.33 (2017 July 18) ====================== -- Raise an exception when uploading an empty file using :meth:`upload`, :meth:`upload_thumbnail` +- Raise an exception when uploading an empty file using :meth:`upload`, :meth:`upload_thumbnail` or :meth:`upload_filmstrip_thumbnail` before calling out to the server. - Multiple enhancements and bugfixes to Mockgun - Added ``nav_search_string()`` and ``nav_search_entity()`` methods as experimental, internal methods for querying SG hierarchy. @@ -258,17 +258,17 @@ v3.0.27 (2016 Feb 18) v3.0.26 (2016 Feb 1) ==================== -- Updating testing framework to use environment variables inconjunction with existing +- Updating testing framework to use environment variables inconjunction with existing ``example_config`` file so that commits and pull requests are automatically run on travis-ci. -- Fix to prevent stripping out case-sensitivity of a URL if the user passes their credentials to +- Fix to prevent stripping out case-sensitivity of a URL if the user passes their credentials to ``config.server`` as an authorization header. v3.0.25 (2016 Jan 12) ===================== -- Add handling for Python versions incompatible with SHA-2 (see `this blog post +- Add handling for Python versions incompatible with SHA-2 (see `this blog post `_). -- Add ``SHOTGUN_FORCE_CERTIFICATE_VALIDATION`` environment variable to prevent disabling certficate +- Add ``SHOTGUN_FORCE_CERTIFICATE_VALIDATION`` environment variable to prevent disabling certficate validation when SHA-2 validation is not available. - Add SSL info to user-agent header. @@ -276,11 +276,11 @@ v3.0.24 (2016 Jan 08) ===================== - Not released. - + v3.0.23 (2015 Oct 26) ===================== -- Fix for `python bug #23371 `_ on Windows loading mimetypes +- Fix for `python bug #23371 `_ on Windows loading mimetypes module (thanks `@patrickwolf `_). - Fix for tests on older versions of python. - Sanitize authentication values before raising error. @@ -288,13 +288,13 @@ v3.0.23 (2015 Oct 26) v3.0.22 (2015 Sept 9) ===================== -- Added method :meth:`text_search` which allows an API client to access the Shotgun global search +- Added method :meth:`text_search` which allows an API client to access the Shotgun global search and auto completer. -- Added method :meth:`activity_stream_read` which allows an API client to access the activity +- Added method :meth:`activity_stream_read` which allows an API client to access the activity stream for a given Shotgun entity. -- Added method :meth:`note_thread_read` which allows an API client to download an entire Note +- Added method :meth:`note_thread_read` which allows an API client to download an entire Note conversation, including Replies and Attachments, using a single API call. -- Added an experimental ``mockgun`` module which can be used to emulate the Shotgun API, for +- Added an experimental ``mockgun`` module which can be used to emulate the Shotgun API, for example inside unit test rigs. - [minor] Improved docstrings. @@ -313,23 +313,23 @@ v3.0.19 (2015 Mar 25) - Add ability to authenticate with Shotgun using ``session_token``. - Add :meth:`get_session_token` method for obtaining token to authenticate with. -- Add new ``AuthenticationFault`` exception type to indicate when server communication has failed +- Add new ``AuthenticationFault`` exception type to indicate when server communication has failed due to authentication reasons. -- Add support for ``SHOTGUN_API_CACERTS`` environment variable to provide location of external +- Add support for ``SHOTGUN_API_CACERTS`` environment variable to provide location of external SSL certificates file. - Fixes and updates to various tests. v3.0.18 (2015 Mar 13) ===================== -- Add ability to query the per-project visibility status for entities, fields and statuses. +- Add ability to query the per-project visibility status for entities, fields and statuses. (requires Shotgun server >= v5.4.4) v3.0.17 (2014 Jul 10) ===================== - Add ability to update ``last_accessed_by_current_user`` on Project. -- Add workaround for `bug #9291 in Python 2.7 `_ affecting +- Add workaround for `bug #9291 in Python 2.7 `_ affecting mimetypes library on Windows. - Add platform and Python version to user-agent (eg. ``shotgun-json (3.0.17); Python 2.7 (Mac)``) @@ -343,7 +343,7 @@ v3.0.16 (2014 May 23) v3.0.15 (2014 Mar 6) ==================== -- Fixed bug which allowed a value of ``None`` for password parameter in +- Fixed bug which allowed a value of ``None`` for password parameter in :meth:`authenticate_human_user` - Add :meth:`follow`, :meth:`unfollow` and :meth:`followers` methods. - Add ability to login as HumanUser. @@ -355,24 +355,24 @@ v3.0.14 (2013 Jun 26) ===================== - added: additional tests for thumbnails. -- added: support for downloading from s3 in :meth:`download_attachment`. Accepts an Attachment - entity dict as a parameter (is still backwards compatible with passing in an Attachment id). -- added: optional ``file_path`` parameter to :meth:`download_attachment` to write data directly to +- added: support for downloading from s3 in :meth:`download_attachment`. Accepts an Attachment + entity dict as a parameter (is still backwards compatible with passing in an Attachment id). +- added: optional ``file_path`` parameter to :meth:`download_attachment` to write data directly to disk instead of loading into memory. (thanks to Adam Goforth `@aag `_) v3.0.13 (2013 Apr 11) ===================== -- fixed: #20856 :meth:`authenticate_human_user` login was sticky and would be used for permissions +- fixed: #20856 :meth:`authenticate_human_user` login was sticky and would be used for permissions and logging. v3.0.12 (2013 Feb 22) ===================== *no tag* -- added: #18171 New ``ca_certs`` argument to the :class:`Shotgun` constructor to specify the +- added: #18171 New ``ca_certs`` argument to the :class:`Shotgun` constructor to specify the certificates to use in SSL validation. -- added: ``setup.py`` doesn't compress the installed ``.egg`` file which makes the +- added: ``setup.py`` doesn't compress the installed ``.egg`` file which makes the ``cacerts.txt`` file accessible. v3.0.11 (2013 Jan 31) @@ -383,21 +383,21 @@ v3.0.11 (2013 Jan 31) v3.0.10 (2013 Jan 25) ===================== -- added: :meth:`add_user_agent()` and :meth:`reset_user_agent` methods to allow client code to add +- added: :meth:`add_user_agent()` and :meth:`reset_user_agent` methods to allow client code to add strings to track. -- added: Changed default ``user-agent`` to include API version. +- added: Changed default ``user-agent`` to include API version. - updated: advanced summarize filter support. - fixed: #19830 :meth:`share_thumbnail` errors when source has no thumbnail. v3.0.9 (2012 Dec 05) ==================== -- added: :meth:`share_thumbnail` method to share the same thumbnail record and media between +- added: :meth:`share_thumbnail` method to share the same thumbnail record and media between entities. -- added: proxy handling to methods that transfer binary data (ie. :meth:`upload`, +- added: proxy handling to methods that transfer binary data (ie. :meth:`upload`, :meth:`upload_thumbnail`, etc.). - updated: default logging level to WARN. -- updated: documentation for :meth:`summarize()` method, previously released but without +- updated: documentation for :meth:`summarize()` method, previously released but without documentation. - fixed: unicode strings not always being encoded correctly. - fixed: :meth:`create()` generates error when ``return_fields`` is None. @@ -411,10 +411,10 @@ v3.0.9.beta2 (2012 Mar 19) ========================== - use relative imports for included libraries when using Python v2.5 or later. -- replace sideband request for ``image`` (thumbnail) field with native support (requires Shotgun - server >= v3.3.0. Request will still work on older versions but fallback to slow sideband +- replace sideband request for ``image`` (thumbnail) field with native support (requires Shotgun + server >= v3.3.0. Request will still work on older versions but fallback to slow sideband method). -- allow setting ``image`` and ``filmstrip_thumbnail`` in data dict on :meth:`create` and +- allow setting ``image`` and ``filmstrip_thumbnail`` in data dict on :meth:`create` and :meth:`update` (thanks `@hughmacdonald `_). - fixed bug causing ``Attachment.tag_list`` to be set to ``"None"`` (str) for uploads. @@ -433,7 +433,7 @@ v3.0.8 (2011 Oct 7) - added the :meth:`summarize` method. - refactored single file into package. - tests added (Thanks to Aaron Morton `@amorton `_). -- return all strings as ascii for backwards compatibility, added ``ensure_ascii`` parameter to +- return all strings as ascii for backwards compatibility, added ``ensure_ascii`` parameter to enable returning unicode. v3.0.7 (2011 Apr 04) @@ -473,7 +473,7 @@ v3.0.2 (2010 Aug 27) v3.0.1 (2010 May 10) ==================== -- :meth:`find`: default sorting to ascending, if not set (instead of requiring +- :meth:`find`: default sorting to ascending, if not set (instead of requiring ascending/descending). - :meth:`upload` and :meth:`upload_thumbnail`: pass auth info through. @@ -481,7 +481,7 @@ v3.0 (2010 May 5) ================= - non-beta! -- add :meth:`batch` method to do multiple :meth:`create`, :meth:`update`, and :meth:`delete` +- add :meth:`batch` method to do multiple :meth:`create`, :meth:`update`, and :meth:`delete` operations in one request to the server (requires Shotgun server to be v1.13.0 or higher). v3.0b8 (2010 Feb 19) @@ -498,7 +498,7 @@ v3.0b7 (2009 Nov 30) v3.0b6 (2009 Oct 20) ==================== -- add support for ``HTTP/1.1 keepalive``, which greatly improves performance for multiple +- add support for ``HTTP/1.1 keepalive``, which greatly improves performance for multiple requests. - add more helpful error if server entered is not ``http`` or ``https`` - add support assigning tags to file uploads (for Shotgun version >= 1.10.6). @@ -522,6 +522,6 @@ v3.0b3 (2009 June 24) - added ``schema_*`` methods for accessing entities and fields. - added support for http proxy servers. - added ``__version__`` string. -- removed ``RECORDS_PER_PAGE`` global (can just set ``records_per_page`` on the Shotgun object +- removed ``RECORDS_PER_PAGE`` global (can just set ``records_per_page`` on the Shotgun object after initializing it). - removed ``api_ver`` from the constructor, as this class is only designed to work with API v3. diff --git a/LICENSE b/LICENSE index a32a5bdcb..716d625d8 100644 --- a/LICENSE +++ b/LICENSE @@ -5,12 +5,12 @@ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - + list of conditions and the following disclaimer. + - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - + - Neither the name of the Shotgun Software Inc nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. @@ -51,4 +51,4 @@ BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE -OF THIS SOFTWARE. \ No newline at end of file +OF THIS SOFTWARE. diff --git a/SECURITY.md b/SECURITY.md index c32c73245..0cf2a2664 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -32,4 +32,4 @@ configurations, reproduction steps, exploit code, impact, etc. ## Additional Information -Please check out the [Flow Production Tracking Security White Paper](https://help.autodesk.com/view/SGSUB/ENU/?guid=SG_Administrator_ar_general_security_ar_security_white_paper_html). \ No newline at end of file +Please check out the [Flow Production Tracking Security White Paper](https://help.autodesk.com/view/SGSUB/ENU/?guid=SG_Administrator_ar_general_security_ar_security_white_paper_html). diff --git a/azure-pipelines-templates/code_style_validation.yml b/azure-pipelines-templates/code_style_validation.yml new file mode 100644 index 000000000..69e82b7e0 --- /dev/null +++ b/azure-pipelines-templates/code_style_validation.yml @@ -0,0 +1,50 @@ +# Copyright (c) 2024, Shotgun Software Inc. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# - Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# - Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# - Neither the name of the Shotgun Software Inc nor the names of its +# contributors may be used to endorse or promote products derived from this +# software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +jobs: +- job: code_style_validation + displayName: Code Style Validation + pool: + vmImage: 'ubuntu-latest' + + steps: + - task: UsePythonVersion@0 + inputs: + versionSpec: 3.9 + addToPath: True + architecture: 'x64' + + - script: | + pip install --upgrade pip setuptools wheel + pip install --upgrade pre-commit + displayName: Install dependencies + + - bash: pre-commit autoupdate + displayName: Update pre-commit hook versions + + - bash: pre-commit run --all + displayName: Validate code with pre-commit diff --git a/azure-pipelines-templates/run-tests.yml b/azure-pipelines-templates/run-tests.yml index edf5ffe4a..831c276ec 100644 --- a/azure-pipelines-templates/run-tests.yml +++ b/azure-pipelines-templates/run-tests.yml @@ -34,7 +34,7 @@ parameters: jobs: # The job will be named after the OS and Azure will suffix the strategy to make it unique # so we'll have a job name "Windows Python27" for example. What's a strategy? Strategies are the - # name of the keys under the strategy.matrix scope. So for each OS we'll have " Python27" and + # name of the keys under the strategy.matrix scope. So for each OS we'll have " Python27" and # " Python37". - job: ${{ parameters.name }} pool: @@ -63,7 +63,7 @@ jobs: # Specifies which version of Python we want to use. That's where the strategy comes in. # Each job will share this set of steps, but each of them will receive a different # $(python.version) - # TODO: We should provide `githubToken` if we want to download a python release. + # TODO: We should provide `githubToken` if we want to download a python release. # Otherwise we may hit the GitHub anonymous download limit. - task: UsePythonVersion@0 inputs: @@ -138,7 +138,7 @@ jobs: Invoke-WebRequest -Uri https://uploader.codecov.io/latest/windows/codecov.exe -Outfile codecov.exe .\codecov.exe -f coverage.xml displayName: Uploading code coverage - - ${{ elseif eq(parameters.name, 'Linux') }}: + - ${{ elseif eq(parameters.name, 'Linux') }}: - script: | curl -Os https://uploader.codecov.io/latest/linux/codecov chmod +x codecov diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 989745755..52e6cfa9c 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -51,6 +51,7 @@ pr: # This here is the list of jobs we want to run for our build. # Jobs run in parallel. jobs: +- template: azure-pipelines-templates/code_style_validation.yml # These are jobs templates, they allow to reduce the redundancy between # variations of the same build. We pass in the image name diff --git a/docs/advanced/iron_python.rst b/docs/advanced/iron_python.rst index 6aac0a6a9..62ad6d791 100644 --- a/docs/advanced/iron_python.rst +++ b/docs/advanced/iron_python.rst @@ -34,4 +34,4 @@ v3.0.20 can be used with IronPython with a little bit of added work: lower level SSL library backing python's network infrastructure is attempting to connect to our servers via SSLv3, which is no longer supported. You can use the code from this gist to force the SSL connections to use a specific protocol. The forked repo linked above has an example of how to - do that to force the use of TLSv1. \ No newline at end of file + do that to force the use of TLSv1. diff --git a/docs/advanced/packaging.rst b/docs/advanced/packaging.rst index d46426e73..8467db9e2 100644 --- a/docs/advanced/packaging.rst +++ b/docs/advanced/packaging.rst @@ -6,7 +6,7 @@ Packaging an application with py2app (or py2exe) You can create standalone applications with Python scripts by using `py2app `_ on OS X or `py2exe `_ on -Windows. This is often done to more easily distribute applications that have a GUI based on +Windows. This is often done to more easily distribute applications that have a GUI based on toolkits like Tk, Qt or others. There are caveats you need to be aware of when creating such an app. @@ -37,4 +37,4 @@ into the Flow Production Tracking connection's constructor:: sg = shotgun_api3.Shotgun('https://my-site.shotgrid.autodesk.com', 'script_name', 'script_key', ca_certs=ca_certs) -The process for py2exe should be similar. \ No newline at end of file +The process for py2exe should be similar. diff --git a/docs/authentication.rst b/docs/authentication.rst index 0e5fe8572..ea049fbd0 100644 --- a/docs/authentication.rst +++ b/docs/authentication.rst @@ -62,4 +62,3 @@ For Scripts, the default permission role is "API Admin User" which allows full a When using user-based authentication in your script, it will be bound by the permission role assigned to you in Flow Production Tracking. For example, if you don't have access to edit the status field on Shots, your script won't be able to either. Attempting to perform actions that are prohibited by permissions will raise an appropriate exception. .. seealso:: `Permissions Documentation `_ - diff --git a/docs/changelog.rst b/docs/changelog.rst index f07fa1f9c..3b4977908 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,3 +1,3 @@ .. currentmodule:: shotgun_api3.shotgun.Shotgun -.. include:: ../HISTORY.rst \ No newline at end of file +.. include:: ../HISTORY.rst diff --git a/docs/cookbook.rst b/docs/cookbook.rst index f69334a7b..fe0a5a300 100644 --- a/docs/cookbook.rst +++ b/docs/cookbook.rst @@ -3,7 +3,7 @@ API Cookbook ************ Here we have a collection of useful information you can use for reference when writing your API -scripts. From usage tips and gotchas to deeper examples of working with entities like Tasks and +scripts. From usage tips and gotchas to deeper examples of working with entities like Tasks and Files, there's a lot of example code in here for you to play with. .. rubric:: Usage Tips @@ -28,7 +28,7 @@ and paste any of these into your own scripts. .. rubric:: Working With Files -You'll probably be doing some work with files at your studio. This is a deep dive into some of +You'll probably be doing some work with files at your studio. This is a deep dive into some of the inners of how Flow Production Tracking handles files (also called Attachments) and the different ways to link to them. @@ -51,12 +51,12 @@ need to do. .. rubric:: Smart Cut Fields -Smart Cut Fields are deprecated in favor of the +Smart Cut Fields are deprecated in favor of the `new cut support added in ShotGrid v7.0 `_. This documentation remains only to support studios who may not have upgraded to the new cut support -features. +features. .. toctree:: :maxdepth: 2 - cookbook/smart_cut_fields \ No newline at end of file + cookbook/smart_cut_fields diff --git a/docs/cookbook/examples/ami_handler.rst b/docs/cookbook/examples/ami_handler.rst index f64ccd558..3fb5e3571 100644 --- a/docs/cookbook/examples/ami_handler.rst +++ b/docs/cookbook/examples/ami_handler.rst @@ -4,15 +4,15 @@ Handling Action Menu Item Calls ############################### -This is an example ActionMenu Python class to handle the ``GET`` request sent from an -ActionMenuItem. It doesn't manage dispatching custom protocols but rather takes the arguments -from any ``GET`` data and parses them into the easily accessible and correctly typed instance +This is an example ActionMenu Python class to handle the ``GET`` request sent from an +ActionMenuItem. It doesn't manage dispatching custom protocols but rather takes the arguments +from any ``GET`` data and parses them into the easily accessible and correctly typed instance variables for your Python scripts. Available as a Gist at https://gist.github.com/3253287 .. seealso:: - Our `support site has more information about Action Menu Items + Our `support site has more information about Action Menu Items `_. ************ diff --git a/docs/cookbook/examples/ami_version_packager.rst b/docs/cookbook/examples/ami_version_packager.rst index 5d3035014..415075a1d 100644 --- a/docs/cookbook/examples/ami_version_packager.rst +++ b/docs/cookbook/examples/ami_version_packager.rst @@ -4,7 +4,7 @@ Using an ActionMenuItem to Package Versions for a Client ######################################################## -This is an example script to demonstrate how you can use an ActionMenuItem to launch a local +This is an example script to demonstrate how you can use an ActionMenuItem to launch a local script to package up files for a client. It performs the following: - Downloads Attachments from a specified field for all selected entities. @@ -37,10 +37,10 @@ It is intended to be used in conjunction with the script dicussed in :ref:`ami_h login who ran the ActionMenuItem ('Demo_Project_2010-04-29-172210_kp.tar.gz'): sa = ShotgunAction(sys.argv[1]) - sg = shotgun_connect() + sg = shotgun_connect() if sa.action == 'package4client': r = packageFilesForClient('sg_qt','/path/where/i/want/to/put/the/archive/') - + """ # --------------------------------------------------------------------------------------------- @@ -61,8 +61,8 @@ It is intended to be used in conjunction with the script dicussed in :ref:`ami_h # --------------------------------------------------------------------------------------------- # Flow Production Tracking server auth info shotgun_conf = { - 'url':'https://my-site.shotgrid.autodesk.com', - 'name':'YOUR_SCRIPT_NAME_HERE', + 'url':'https://my-site.shotgrid.autodesk.com', + 'name':'YOUR_SCRIPT_NAME_HERE', 'key':'YOUR_SCRIPT_KEY_HERE' } @@ -70,9 +70,9 @@ It is intended to be used in conjunction with the script dicussed in :ref:`ami_h logfile = os.path.dirname(sys.argv[0])+"/version_packager.log" # temporary directory to download movie files to and create thumbnail files in - file_dir = os.path.dirname(sys.argv[0])+"/tmp" + file_dir = os.path.dirname(sys.argv[0])+"/tmp" - # compress command + # compress command # tar czf /home/user/backup_www.tar.gz -C / var/www/html compress_cmd = "tar czf %s -C / %s" @@ -89,7 +89,7 @@ It is intended to be used in conjunction with the script dicussed in :ref:`ami_h # ---------------------------------------------- # Set up logging # ---------------------------------------------- - def init_log(filename="version_packager.log"): + def init_log(filename="version_packager.log"): try: logger.basicConfig(level=logger.DEBUG, format='%(asctime)s %(levelname)-8s %(message)s', @@ -98,8 +98,8 @@ It is intended to be used in conjunction with the script dicussed in :ref:`ami_h filemode='w+') except IOError, e: raise ShotgunException ("Unable to open logfile for writing: %s" % e) - logger.info("Version Packager logging started.") - return logger + logger.info("Version Packager logging started.") + return logger # ---------------------------------------------- @@ -111,9 +111,9 @@ It is intended to be used in conjunction with the script dicussed in :ref:`ami_h try: attachment_id = int(attachment_id) except: - # not an integer. + # not an integer. return None - # raise ShotgunException("invalid Attachment id returned. Expected an integer: %s "% attachment_id) + # raise ShotgunException("invalid Attachment id returned. Expected an integer: %s "% attachment_id) return attachment_id @@ -126,16 +126,16 @@ It is intended to be used in conjunction with the script dicussed in :ref:`ami_h if type(attachment_id) != int: return None # download the attachment file from Flow Production Tracking and write it to local disk - logger.info("Downloading Attachment #%s" % (attachment_id)) + logger.info("Downloading Attachment #%s" % (attachment_id)) stream = sg.download_attachment(attachment_id) try: file = open(destination_filename, 'w') file.write(stream) file.close() logger.info("Downloaded attachment %s" % (destination_filename)) - return True + return True except e: - raise ShotgunException("unable to write attachment to disk: %s"% e) + raise ShotgunException("unable to write attachment to disk: %s"% e) # ---------------------------------------------- @@ -194,28 +194,28 @@ It is intended to be used in conjunction with the script dicussed in :ref:`ami_h logger.info("copied files to: %s" % destination_directory) return destination_directory - - + + def packageFilesForClient(file_field,destination_dir): - - # get entities matching the selected ids - logger.info("Querying Shotgun for %s %ss" % (len(sa.selected_ids_filter), sa.params['entity_type'])) + + # get entities matching the selected ids + logger.info("Querying Shotgun for %s %ss" % (len(sa.selected_ids_filter), sa.params['entity_type'])) entities = sg.find(sa.params['entity_type'],sa.selected_ids_filter,['id','code',file_field],filter_operator='any') - + # download the attachments for each entity, zip them, and copy to destination directory files = [] for e in entities: if not e[file_field]: - logger.info("%s #%s: No file exists. Skippinsa." % (sa.params['entity_type'], e['id'])) + logger.info("%s #%s: No file exists. Skippinsa." % (sa.params['entity_type'], e['id'])) else: - logger.info("%s #%s: %s" % (sa.params['entity_type'], e['id'], e[file_field])) + logger.info("%s #%s: %s" % (sa.params['entity_type'], e['id'], e[file_field])) path_to_file = file_dir+"/"+re.sub(r"\s+", '_', e[file_field]['name']) - result = download_attachment_to_disk(e[file_field], path_to_file ) - + result = download_attachment_to_disk(e[file_field], path_to_file ) + # only include attachments. urls won't return true if result: files.append(path_to_file) - + # compress files # create a nice valid destination filename project_name = '' @@ -223,7 +223,7 @@ It is intended to be used in conjunction with the script dicussed in :ref:`ami_h project_name = re.sub(r"\s+", '_', sa.params['project_name'])+'_' dest_filename = project_name+datetime.today().strftime('%Y-%m-%d-%H%M%S')+"_"+sa.params['user_login'] archive = compress_files(files,file_dir+"/"+dest_filename) - + # now that we have the archive, remove the downloads r = remove_downloaded_files(files) @@ -232,26 +232,25 @@ It is intended to be used in conjunction with the script dicussed in :ref:`ami_h return True - + # ---------------------------------------------- # Main Block # ---------------------------------------------- if __name__ == "__main__": init_log(logfile) - + try: sa = ShotgunAction(sys.argv[1]) logger.info("Firing... %s" % (sys.argv[1]) ) except IndexError, e: raise ShotgunException("Missing POST arguments") - - sg = Shotgun(shotgun_conf['url'], shotgun_conf['name'], shotgun_conf['key'],convert_datetimes_to_utc=convert_tz) - + + sg = Shotgun(shotgun_conf['url'], shotgun_conf['name'], shotgun_conf['key'],convert_datetimes_to_utc=convert_tz) + if sa.action == 'package4client': result = packageFilesForClient('sg_qt','/Users/kp/Documents/shotgun/dev/api/files/') else: raise ShotgunException("Unknown action... :%s" % sa.action) - - - print("\nVersion Packager done!") + + print("\nVersion Packager done!") diff --git a/docs/cookbook/examples/basic_create_shot.rst b/docs/cookbook/examples/basic_create_shot.rst index 4a9ece5f6..7513305fa 100644 --- a/docs/cookbook/examples/basic_create_shot.rst +++ b/docs/cookbook/examples/basic_create_shot.rst @@ -7,28 +7,28 @@ Building the data and calling :meth:`~shotgun_api3.Shotgun.create` ------------------------------------------------------------------ To create a Shot, you need to provide the following values: -- ``project`` is a link to the Project the Shot belongs to. It should be a dictionary like +- ``project`` is a link to the Project the Shot belongs to. It should be a dictionary like ``{"type": "Project", "id": 123}`` where ``id`` is the ``id`` of the Project. - ``code`` (this is the field that stores the name Shot) - optionally any other info you want to provide Example:: - data = { + data = { 'project': {"type":"Project","id": 4}, 'code': '100_010', 'description': 'Open on a beautiful field with fuzzy bunnies', - 'sg_status_list': 'ip' + 'sg_status_list': 'ip' } result = sg.create('Shot', data) This will create a new Shot named "100_010" in the Project "Gunslinger" (which has an ``id`` of 4). -- ``data`` is a list of key/value pairs where the key is the column name to update and the value +- ``data`` is a list of key/value pairs where the key is the column name to update and the value is the the value to set. - ``sg`` is the Flow Production Tracking API instance you created in :ref:`example_sg_instance`. -- ``create()`` is the :meth:`shotgun_api3.Shotgun.create` API method we are calling. We pass in the +- ``create()`` is the :meth:`shotgun_api3.Shotgun.create` API method we are calling. We pass in the entity type we're searching for and the data we're setting. .. rubric:: Result @@ -67,27 +67,27 @@ The Complete Example # Globals # -------------------------------------- # make sure to change this to match your Flow Production Tracking server and auth credentials. - SERVER_PATH = "https://my-site.shotgrid.autodesk.com" - SCRIPT_NAME = 'my_script' + SERVER_PATH = "https://my-site.shotgrid.autodesk.com" + SCRIPT_NAME = 'my_script' SCRIPT_KEY = '27b65d7063f46b82e670fe807bd2b6f3fd1676c1' # -------------------------------------- - # Main + # Main # -------------------------------------- - if __name__ == '__main__': + if __name__ == '__main__': sg = shotgun_api3.Shotgun(SERVER_PATH, SCRIPT_NAME, SCRIPT_KEY) # -------------------------------------- # Create a Shot with data # -------------------------------------- - data = { + data = { 'project': {"type":"Project","id": 4}, 'code': '100_010', 'description': 'Open on a beautiful field with fuzzy bunnies', - 'sg_status_list': 'ip' + 'sg_status_list': 'ip' } - result = sg.create('Shot', data) + result = sg.create('Shot', data) pprint(result) print("The id of the {} is {}.".format(result['type'], result['id'])) @@ -100,4 +100,3 @@ And here is the output:: 'sg_status_list': 'ip', 'type': 'Shot'} The id of the Shot is 40435. - diff --git a/docs/cookbook/examples/basic_create_shot_task_template.rst b/docs/cookbook/examples/basic_create_shot_task_template.rst index 0fa3b0828..ab6248227 100644 --- a/docs/cookbook/examples/basic_create_shot_task_template.rst +++ b/docs/cookbook/examples/basic_create_shot_task_template.rst @@ -14,7 +14,7 @@ First we need to find the Task Template we're going to apply. We'll assume you k The Resulting Task Template --------------------------- -Assuming the task template was found, we will now have something like this in our ``template`` +Assuming the task template was found, we will now have something like this in our ``template`` variable:: {'type': 'TaskTemplate', 'id': 12} @@ -30,16 +30,16 @@ Now we can create the Shot with the link to the ``TaskTemplate`` to apply. 'task_template': template } result = sg.create('Shot', data) -This will create a new Shot named "100_010" linked to the TaskTemplate "3D Shot Template" and +This will create a new Shot named "100_010" linked to the TaskTemplate "3D Shot Template" and Flow Production Tracking will then create the Tasks defined in the template and link them to the Shot you just created. -- ``data`` is a list of key/value pairs where the key is the column name to update and the value is +- ``data`` is a list of key/value pairs where the key is the column name to update and the value is the value. - ``project`` and `code` are required - ``description`` is just a text field that you might want to update as well -- ``task_template`` is another entity column where we set the Task Template which has the Tasks we - wish to create by default on this Shot. We found the specific template we wanted to assign in the +- ``task_template`` is another entity column where we set the Task Template which has the Tasks we + wish to create by default on this Shot. We found the specific template we wanted to assign in the previous block by searching Result @@ -59,7 +59,7 @@ The variable ``result`` now contains the dictionary of the new Shot that was cre } -If we now search for the Tasks linked to the Shot, we'll find the Tasks that match our +If we now search for the Tasks linked to the Shot, we'll find the Tasks that match our ``TaskTemplate``:: tasks = sg.find('Task', filters=[['entity', 'is', result]]) diff --git a/docs/cookbook/examples/basic_create_version_link_shot.rst b/docs/cookbook/examples/basic_create_version_link_shot.rst index 71e6f3e1e..3f025eb46 100644 --- a/docs/cookbook/examples/basic_create_version_link_shot.rst +++ b/docs/cookbook/examples/basic_create_version_link_shot.rst @@ -5,7 +5,7 @@ new ``Version`` entity linked to the Shot. Find the Shot ------------- -First we need to find the Shot since we'll need to know know its ``id`` in order to link our Version +First we need to find the Shot since we'll need to know know its ``id`` in order to link our Version to it. :: @@ -16,8 +16,8 @@ to it. Find the Task ------------- -Now we find the Task that the Version relates to, again so we can use the ``id`` to link it to the -Version we're creating. For this search we'll use the Shot ``id`` (which we have now in the ``shot`` +Now we find the Task that the Version relates to, again so we can use the ``id`` to link it to the +Version we're creating. For this search we'll use the Shot ``id`` (which we have now in the ``shot`` variable from the previous search) and the Task Name, which maps to the ``content`` field. :: @@ -27,7 +27,7 @@ variable from the previous search) and the Task Name, which maps to the ``conten task = sg.find_one('Task', filters) .. note:: Linking a Task to the Version is good practice. By doing so it is easy for users to see - at what stage a particular Version was created, and opens up other possibilities for tracking + at what stage a particular Version was created, and opens up other possibilities for tracking in Flow Production Tracking. We highly recommend doing this whenever possible. Create the Version @@ -44,22 +44,22 @@ Now we can create the Version with the link to the Shot and the Task:: 'user': {'type': 'HumanUser', 'id': 165} } result = sg.create('Version', data) -This will create a new Version named '100_010_anim_v1' linked to the 'Animation' Task for Shot +This will create a new Version named '100_010_anim_v1' linked to the 'Animation' Task for Shot '100_010' in the Project 'Gunslinger'. -- ``data`` is a list of key/value pairs where the key is the column name to update and the value is +- ``data`` is a list of key/value pairs where the key is the column name to update and the value is the value to set. - ``project`` and ``code`` are required -- ``description`` and ``sg_path_to_frames`` are just text fields that you might want to update as +- ``description`` and ``sg_path_to_frames`` are just text fields that you might want to update as well -- ``sg_status_list`` is the status column for the Version. Here we are setting it to "rev" (Pending +- ``sg_status_list`` is the status column for the Version. Here we are setting it to "rev" (Pending Review) so that it will get reviewed in the next dailies session and people will "ooh" and "aaah". -- ``entity`` is where we link this version to the Shot. Entity columns are always handled with this +- ``entity`` is where we link this version to the Shot. Entity columns are always handled with this format. You must provide the entity ``type`` and its ``id``. -- ``sg_task`` is another entity link field specifically for the Version's Task link. This uses the +- ``sg_task`` is another entity link field specifically for the Version's Task link. This uses the same entity format as the Shot link, but pointing to the Task entity this time. -- ``user`` is another entity column where we set the artist responsible for this masterpiece. In - this example, I know the 'id' that corresponds to this user, but if you don't know the id you can +- ``user`` is another entity column where we set the artist responsible for this masterpiece. In + this example, I know the 'id' that corresponds to this user, but if you don't know the id you can look it up by searching on any of the fields, similar to what we did for the Shot above, like:: filters = [['login', 'is', 'jschmoe']] @@ -72,11 +72,11 @@ The ``result`` variable now contains the ``id`` of the new Version that was crea Upload a movie for review in Screening Room ------------------------------------------- -If Screening Room's transcoding feature is enabled on your site (hosted sites have this by -default), then you can use the :meth:`~shotgun_api3.Shotgun.upload` method to upload a QuickTime -movie, PDF, still image, etc. to the ``sg_uploaded_movie`` field on a Version. Once the movie is -uploaded, it will automatically be queued for transcoding. When transcoding is complete, the -Version will be playable in the Screening Room app, or in the Overlay player by clicking on the +If Screening Room's transcoding feature is enabled on your site (hosted sites have this by +default), then you can use the :meth:`~shotgun_api3.Shotgun.upload` method to upload a QuickTime +movie, PDF, still image, etc. to the ``sg_uploaded_movie`` field on a Version. Once the movie is +uploaded, it will automatically be queued for transcoding. When transcoding is complete, the +Version will be playable in the Screening Room app, or in the Overlay player by clicking on the Play button that will appear on the Version's thumbnail. -.. note:: Transcoding also generates a thumbnail and filmstrip thumbnail automatically. \ No newline at end of file +.. note:: Transcoding also generates a thumbnail and filmstrip thumbnail automatically. diff --git a/docs/cookbook/examples/basic_delete_shot.rst b/docs/cookbook/examples/basic_delete_shot.rst index 5275735d6..4f2e91018 100644 --- a/docs/cookbook/examples/basic_delete_shot.rst +++ b/docs/cookbook/examples/basic_delete_shot.rst @@ -5,7 +5,7 @@ Calling :meth:`~shotgun_api3.Shotgun.delete` -------------------------------------------- Deleting an entity in Flow Production Tracking is pretty straight-forward. No extraneous steps required.:: - result = sg.delete("Shot", 40435) + result = sg.delete("Shot", 40435) Result ------ @@ -30,23 +30,22 @@ The Complete Example # -------------------------------------- # make sure to change this to match your Flow Production Tracking server and auth credentials. SERVER_PATH = "https://my-site.shotgrid.autodesk.com" - SCRIPT_NAME = 'my_script' + SCRIPT_NAME = 'my_script' SCRIPT_KEY = '27b65d7063f46b82e670fe807bd2b6f3fd1676c1' # -------------------------------------- - # Main + # Main # -------------------------------------- - if __name__ == '__main__': + if __name__ == '__main__': sg = shotgun_api3.Shotgun(SERVER_PATH, SCRIPT_NAME, SCRIPT_KEY) # -------------------------------------- # Delete a Shot by id # -------------------------------------- - result = sg.delete("Shot", 40435) + result = sg.delete("Shot", 40435) pprint(result) And here is the output:: True - diff --git a/docs/cookbook/examples/basic_find_shot.rst b/docs/cookbook/examples/basic_find_shot.rst index 88c8c81e0..945eb1be6 100644 --- a/docs/cookbook/examples/basic_find_shot.rst +++ b/docs/cookbook/examples/basic_find_shot.rst @@ -12,7 +12,7 @@ We are going to assume we know the 'id' of the Shot we're looking for in this ex Pretty simple right? Well here's a little more insight into what's going on. -- ``filters`` is an list of filter conditions. In this example we are filtering for Shots where +- ``filters`` is an list of filter conditions. In this example we are filtering for Shots where the ``id`` column is **40435**. - ``sg`` is the Flow Production Tracking API instance. - ``find_one()`` is the :meth:`~shotgun_api3.Shotgun.find_one` API method we are calling. We @@ -25,13 +25,13 @@ So what does this return? The variable result now contains:: {'type': 'Shot','id': 40435} -By default, :meth:`~shotgun_api3.Shotgun.find_one` returns a single dictionary object with +By default, :meth:`~shotgun_api3.Shotgun.find_one` returns a single dictionary object with the ``type`` and ``id`` fields. So in this example, we found a Shot matching that id, and Flow Production Tracking returned it as a dictionary object with ``type`` and ``id`` keys . -How do we know that result contains the Shot dictionary object? You can trust us... but just to be -sure, the :mod:`pprint` (PrettyPrint) module from the Python standard library is a really good tool -to help with debugging. It will print out objects in a nicely formatted way that makes things +How do we know that result contains the Shot dictionary object? You can trust us... but just to be +sure, the :mod:`pprint` (PrettyPrint) module from the Python standard library is a really good tool +to help with debugging. It will print out objects in a nicely formatted way that makes things easier to read. So we'll add that to the import section of our script.:: import shotgun_api3 @@ -54,13 +54,13 @@ The Complete Example # -------------------------------------- # make sure to change this to match your Flow Production Tracking server and auth credentials. SERVER_PATH = "https://my-site.shotgrid.autodesk.com" - SCRIPT_NAME = 'my_script' + SCRIPT_NAME = 'my_script' SCRIPT_KEY = '27b65d7063f46b82e670fe807bd2b6f3fd1676c1' # -------------------------------------- - # Main + # Main # -------------------------------------- - if __name__ == '__main__': + if __name__ == '__main__': sg = shotgun_api3.Shotgun(SERVER_PATH, SCRIPT_NAME, SCRIPT_KEY) @@ -68,7 +68,7 @@ The Complete Example # Find a Shot by id # -------------------------------------- filters = [['id', 'is', 40435]] - result = sg.find_one('Shot', filters) + result = sg.find_one('Shot', filters) pprint(result) And here is the output:: diff --git a/docs/cookbook/examples/basic_sg_instance.rst b/docs/cookbook/examples/basic_sg_instance.rst index b39c78432..d17b57de5 100644 --- a/docs/cookbook/examples/basic_sg_instance.rst +++ b/docs/cookbook/examples/basic_sg_instance.rst @@ -13,14 +13,14 @@ authentication. ``sg`` represents your Flow Production Tracking API instance. Be import shotgun_api3 SERVER_PATH = "https://my-site.shotgrid.autodesk.com" - SCRIPT_NAME = 'my_script' + SCRIPT_NAME = 'my_script' SCRIPT_KEY = '27b65d7063f46b82e670fe807bd2b6f3fd1676c1' sg = shotgun_api3.Shotgun(SERVER_PATH, SCRIPT_NAME, SCRIPT_KEY) - # Just for demo purposes, this will print out property and method names available on the + # Just for demo purposes, this will print out property and method names available on the # sg connection object pprint.pprint([symbol for symbol in sorted(dir(sg)) if not symbol.startswith('_')]) For further information on what you can do with this Flow Production Tracking object you can read the -:ref:`API reference `. \ No newline at end of file +:ref:`API reference `. diff --git a/docs/cookbook/examples/basic_update_shot.rst b/docs/cookbook/examples/basic_update_shot.rst index 52e57e70e..c2413c3ee 100644 --- a/docs/cookbook/examples/basic_update_shot.rst +++ b/docs/cookbook/examples/basic_update_shot.rst @@ -3,23 +3,23 @@ Update A Shot Building the data and calling :meth:`~shotgun_api3.Shotgun.update` ------------------------------------------------------------------ -To update a Shot, you need to provide the ``id`` of the Shot and a list of fields you want to +To update a Shot, you need to provide the ``id`` of the Shot and a list of fields you want to update.:: - data = { + data = { 'description': 'Open on a beautiful field with fuzzy bunnies', - 'sg_status_list': 'ip' + 'sg_status_list': 'ip' } result = sg.update('Shot', 40435, data) -This will update the ``description`` and the ``sg_status_list`` fields for the Shot with ``id`` of +This will update the ``description`` and the ``sg_status_list`` fields for the Shot with ``id`` of **40435**. - ``data`` is a list of key/value pairs where the key is the field name to update and the value to update it to. - ``sg`` is the Flow Production Tracking API instance. -- ``update()`` is the :meth:`shotgun_api3.Shotgun.update` API method we are calling. We provide it - with the entity type we're updating, the ``id`` of the entity, and the data we're updating it +- ``update()`` is the :meth:`shotgun_api3.Shotgun.update` API method we are calling. We provide it + with the entity type we're updating, the ``id`` of the entity, and the data we're updating it with. Result @@ -34,7 +34,7 @@ The variable ``result`` now contains the Shot object that with the updated value } In addition, Flow Production Tracking has returned the ``id`` for the Shot, as well as a ``type`` value. ``type`` -is provided for convenience simply to help you identify what entity type this dictionary represents. +is provided for convenience simply to help you identify what entity type this dictionary represents. It does not correspond to any field in Flow Production Tracking. Flow Production Tracking will *always* return the ``id`` and ``type`` keys in the dictionary when there are results @@ -57,24 +57,24 @@ The Complete Example # -------------------------------------- # make sure to change this to match your Flow Production Tracking server and auth credentials. SERVER_PATH = "https://my-site.shotgrid.autodesk.com" - SCRIPT_NAME = 'my_script' + SCRIPT_NAME = 'my_script' SCRIPT_KEY = '27b65d7063f46b82e670fe807bd2b6f3fd1676c1' # -------------------------------------- - # Main + # Main # -------------------------------------- - if __name__ == '__main__': + if __name__ == '__main__': sg = shotgun_api3.Shotgun(SERVER_PATH, SCRIPT_NAME, SCRIPT_KEY) # -------------------------------------- # Update Shot with data # -------------------------------------- - data = { + data = { 'description': 'Open on a beautiful field with fuzzy bunnies', - 'sg_status_list': 'ip' + 'sg_status_list': 'ip' } - result = sg.update('Shot', 40435, data) + result = sg.update('Shot', 40435, data) pprint(result) And here is the output:: @@ -83,4 +83,3 @@ And here is the output:: 'id': 40435, 'sg_status_list': 'ip', 'type': 'Shot'} - diff --git a/docs/cookbook/examples/basic_upload_thumbnail_version.rst b/docs/cookbook/examples/basic_upload_thumbnail_version.rst index 2ae399d5f..ba7337150 100644 --- a/docs/cookbook/examples/basic_upload_thumbnail_version.rst +++ b/docs/cookbook/examples/basic_upload_thumbnail_version.rst @@ -2,11 +2,11 @@ Upload a Thumbnail for a Version ================================ So you've created a new Version of a Shot, and you've updated Flow Production Tracking, but now you want to upload a -beauty frame to display as the thumbnail for your Version. We'll assume you already have the image -made (located on your machine at ``/v1/gun/s100/010/beauties/anim/100_010_animv1.jpg``) . And since +beauty frame to display as the thumbnail for your Version. We'll assume you already have the image +made (located on your machine at ``/v1/gun/s100/010/beauties/anim/100_010_animv1.jpg``) . And since you've just created your Version in Flow Production Tracking, you know its ``id`` is **214**. -.. note:: If you upload a movie file or image to the ``sg_uploaded_movie`` field and you have +.. note:: If you upload a movie file or image to the ``sg_uploaded_movie`` field and you have transcoding enabled on your server (the default for hosted sites), a thumbnail will be generated automatically as well as a filmstrip thumbnail (if possible). This is a basic example of how to manually provide or replace a thumbnail image. @@ -21,6 +21,6 @@ Upload the Image using :meth:`~shotgun_api3.Shotgun.upload_thumbnail` Flow Production Tracking will take care of resizing the thumbnail for you. If something does go wrong, an exception will be thrown and you'll see the error details. -.. note:: The result returned by :meth:`~shotgun_api3.Shotgun.upload_thumbnail` is an integer +.. note:: The result returned by :meth:`~shotgun_api3.Shotgun.upload_thumbnail` is an integer representing the id of a special ``Attachment`` entity in Flow Production Tracking. Working with Attachments - is beyond the scope of this example. :) \ No newline at end of file + is beyond the scope of this example. :) diff --git a/docs/cookbook/examples/svn_integration.rst b/docs/cookbook/examples/svn_integration.rst index 9a877b322..8b0a6ce46 100644 --- a/docs/cookbook/examples/svn_integration.rst +++ b/docs/cookbook/examples/svn_integration.rst @@ -9,26 +9,26 @@ Integrating Flow Production Tracking with Subversion consists of two basic parts - Setup a post-commit hook in Subversion. - Create a Flow Production Tracking API script to create the Revision in Flow Production Tracking. This script will be called by the post-commit hook. - + **************** Post-Commit Hook **************** To setup the post-commit hook: -- Locate the ``post-commit.tmpl`` file, which is found inside the ``hooks`` folder in your - repository directory. This is a template script that has lots of useful comments and can serve +- Locate the ``post-commit.tmpl`` file, which is found inside the ``hooks`` folder in your + repository directory. This is a template script that has lots of useful comments and can serve as a starting point for the real thing. -- Create your very own executable script, and save it in the same ``hooks`` folder, name it +- Create your very own executable script, and save it in the same ``hooks`` folder, name it ``post-commit``, and give it executable permission. - In your ``post-commit`` script, invoke your Flow Production Tracking API script. -If this is entirely new to you, we highly suggest reading up on the topic. O'Reilly has `a free -online guide for Subversion 1.5 and 1.6 +If this is entirely new to you, we highly suggest reading up on the topic. O'Reilly has `a free +online guide for Subversion 1.5 and 1.6 `_ -Here's an example of a post-commit hook that we've made for Subversion 1.6 using an executable -Unix shell script. The last line invokes "shotgun_api_script.py" which is our Python script that +Here's an example of a post-commit hook that we've made for Subversion 1.6 using an executable +Unix shell script. The last line invokes "shotgun_api_script.py" which is our Python script that will do all the heavy lifting. Lines 4 thru 8 queue up some objects that we'll use later on. .. code-block:: sh @@ -48,13 +48,13 @@ will do all the heavy lifting. Lines 4 thru 8 queue up some objects that we'll Explanation of selected lines ============================= -- lines ``4-5``: After the commit, Subversion leaves us two string objects in the environment: - ``REPOS`` and ``REV`` (the repository path and the revision number, respectively). -- lines ``7-8``: Here we use the shell command ``export`` to create two more string objects in the - environment: ``AUTHOR`` and ``COMMENT``. To get each value, we use the ``svnlook`` command with - our ``REPOS`` and ``REV`` values, first with the ``author``, and then with ``log`` subcommand. - These are actually the first two original lines of code - everything else to this point was - pre-written already in the ``post-commit.tmpl`` file. nice :) +- lines ``4-5``: After the commit, Subversion leaves us two string objects in the environment: + ``REPOS`` and ``REV`` (the repository path and the revision number, respectively). +- lines ``7-8``: Here we use the shell command ``export`` to create two more string objects in the + environment: ``AUTHOR`` and ``COMMENT``. To get each value, we use the ``svnlook`` command with + our ``REPOS`` and ``REV`` values, first with the ``author``, and then with ``log`` subcommand. + These are actually the first two original lines of code - everything else to this point was + pre-written already in the ``post-commit.tmpl`` file. nice :) - line ``10``: This is the absolute path to our Flow Production Tracking API Script. *********************************** @@ -63,7 +63,7 @@ Flow Production Tracking API Script This script will create the Revision and populate it with some metadata using the Flow Production Tracking Python API. It will create our Revision in Flow Production Tracking along with the author, comment, and because we use -Trac (a web-based interface for Subversion), it will also populate a URL field with a clickable +Trac (a web-based interface for Subversion), it will also populate a URL field with a clickable link to the Revision. .. code-block:: python @@ -84,27 +84,27 @@ link to the Revision. # Globals - update all of these values to those of your studio # --------------------------------------------------------------------------------------------- SERVER_PATH = 'https ://my-site.shotgrid.autodesk.com' # or http: - SCRIPT_USER = 'script_name' + SCRIPT_USER = 'script_name' SCRIPT_KEY = '3333333333333333333333333333333333333333' REVISIONS_PATH = 'https ://serveraddress/trac/changeset/' # or other web-based UI PROJECT = {'type':'Project', 'id':27} - + # --------------------------------------------------------------------------------------------- # Main # --------------------------------------------------------------------------------------------- if __name__ == '__main__': sg = Shotgun(SERVER_PATH, SCRIPT_USER, SCRIPT_KEY) - + # Set Python variables from the environment objects revision_code = os.environ['REV'] repository = os.environ['REPOS'] description = os.environ['COMMENT'] author = os.environ['AUTHOR'] - + # Set the Trac path for this specific revision revision_url = REVISIONS_PATH + revision_code - + # Validate that author is a valid Flow Production Tracking HumanUser result = sg.find_one("HumanUser", [['login', 'is', author]]) if result: @@ -118,7 +118,7 @@ link to the Revision. } revision = sg.create("Revision", parameters) print("created Revision #"+str(revision_code)) - + # Send error message if valid HumanUser is not found else: print("Unable to find a valid Flow Production Tracking User with login: {}, Revision not created in Flow Production Tracking.".format(author)) @@ -131,16 +131,16 @@ Explanation of selected lines: - line ``14``: This should be the URL to your instance of Flow Production Tracking. - lines ``15-16``: Make sure you get these values from the "Scripts" page in the Admin section of the Flow Production Tracking web application. If you're not sure how to do this, check out :doc:`authentication`. -- line ``17``: This is the address of Trac, our web-based interface that we use with Subversion. - You may use a different interface, or none at all, so feel free to adjust this line or ignore it +- line ``17``: This is the address of Trac, our web-based interface that we use with Subversion. + You may use a different interface, or none at all, so feel free to adjust this line or ignore it as your case may be. - line ``18``: Every Revision in Flow Production Tracking must have a Project, which is passed to the API as a - dictionary with two keys, the ``type`` and the ``id``. Of course the ``type`` value will always - remain ``Project`` (case sensitive), but the ``id`` will change by Project. To find out the + dictionary with two keys, the ``type`` and the ``id``. Of course the ``type`` value will always + remain ``Project`` (case sensitive), but the ``id`` will change by Project. To find out the ``id`` of your Project, go to the Projects page in the Flow Production Tracking web application, locate the - Project where you want your Revisions created, and then locate its ``id`` field (which you may - need to display - if you don't see it, right click on any column header then select - "Insert Column" > "Id"). Note that for this example we assume that all Revisions in this + Project where you want your Revisions created, and then locate its ``id`` field (which you may + need to display - if you don't see it, right click on any column header then select + "Insert Column" > "Id"). Note that for this example we assume that all Revisions in this Subversion repository will belong to the same Project. - lines ``28-31``: Grab the values from the objects that were left for us in the environment. - line ``34``: Add the Revision number to complete the path of our Trac url. @@ -148,10 +148,10 @@ Explanation of selected lines: Users' Flow Production Tracking logins match their Subversion names. If the user exists in Flow Production Tracking, that user's ``id`` will be returned as ``result['id']``, which we will need later on in line 46. - lines ``40-48``: Use all the meta data we've gathered to create a Revision in Flow Production Tracking. If none - of these lines make any sense, check out more on the :meth:`~shotgun_api3.Shotgun.create` method - here. Line 41 deserves special mention: notice that we define a dictionary called ``url`` that - has three important keys: ``content_type``, ``url``, and ``name``, and we then pass this in as - the value for the ``attachment`` field when we create the Revision. If you're even in doubt, + of these lines make any sense, check out more on the :meth:`~shotgun_api3.Shotgun.create` method + here. Line 41 deserves special mention: notice that we define a dictionary called ``url`` that + has three important keys: ``content_type``, ``url``, and ``name``, and we then pass this in as + the value for the ``attachment`` field when we create the Revision. If you're even in doubt, double check the syntax and requirements for the different field types here. *************** @@ -161,8 +161,8 @@ Troubleshooting My post-commit script is simply not running. I can run it manually, but commits are not triggering it. ====================================================================================================== -Make sure that the script is has explicitly been made executable and that all users who will -invoke it have appropriate permissions for the script and that folders going back to root. +Make sure that the script is has explicitly been made executable and that all users who will +invoke it have appropriate permissions for the script and that folders going back to root. My Flow Production Tracking API script is not getting called by the post-commit hook. ===================================================================================== diff --git a/docs/cookbook/smart_cut_fields.rst b/docs/cookbook/smart_cut_fields.rst index 0ee74d189..928cf3b53 100644 --- a/docs/cookbook/smart_cut_fields.rst +++ b/docs/cookbook/smart_cut_fields.rst @@ -9,16 +9,16 @@ Smart Cut Fields cut support. `Read the Cut Support Documentation here `_. If you want to work with 'smart' cut fields through the API, your script should use a corresponding -'raw' fields for all updates. The 'smart_cut_fields' are primarily for display in the UI, the real +'raw' fields for all updates. The 'smart_cut_fields' are primarily for display in the UI, the real data is stored in a set of 'raw' fields that have different names. ************ Smart Fields ************ -In the UI these fields attempt to calculate values based on data entered into the various fields. -These fields can be queried via the API using the find() method, but not updated. Note that we are -deprecating this feature and recommend creating your own cut fields from scratch, which will not +In the UI these fields attempt to calculate values based on data entered into the various fields. +These fields can be queried via the API using the find() method, but not updated. Note that we are +deprecating this feature and recommend creating your own cut fields from scratch, which will not contain any calculations which have proven to be too magical at times. - ``smart_cut_duration`` diff --git a/docs/cookbook/tasks.rst b/docs/cookbook/tasks.rst index 0acb5f5e7..dd3a1426d 100644 --- a/docs/cookbook/tasks.rst +++ b/docs/cookbook/tasks.rst @@ -2,12 +2,12 @@ Working With Tasks ################## -Tasks have various special functionality available in the UI that can also be queried and +Tasks have various special functionality available in the UI that can also be queried and manipulated through the API. The sections below cover these topics. .. toctree:: :maxdepth: 2 - + tasks/updating_tasks tasks/task_dependencies tasks/split_tasks diff --git a/docs/cookbook/tasks/split_tasks.rst b/docs/cookbook/tasks/split_tasks.rst index 45dfc303f..d16c50e94 100644 --- a/docs/cookbook/tasks/split_tasks.rst +++ b/docs/cookbook/tasks/split_tasks.rst @@ -4,15 +4,15 @@ Split Tasks ########### -Split tasks can be created and edited via the API but must comply to some rules. Before going +Split tasks can be created and edited via the API but must comply to some rules. Before going further, a good understanding of :ref:`how Flow Production Tracking handles task dates is useful `. ******** Overview ******** -The Task entity has a field called ``splits`` which is a list of dictionaries. Each dictionary -in the list has two string keys, ``start`` and ``end``, who's values are strings representing dates +The Task entity has a field called ``splits`` which is a list of dictionaries. Each dictionary +in the list has two string keys, ``start`` and ``end``, who's values are strings representing dates in the ``YYYY-mm-dd`` format. :: @@ -21,11 +21,11 @@ in the ``YYYY-mm-dd`` format. - Splits should be ordered from eldest to newest. - There should be gaps between each split. - - - Gaps are defined as at least one working day. Non-workdays such as weekends and holidays + + - Gaps are defined as at least one working day. Non-workdays such as weekends and holidays are not gaps. -If there are multiple splits but there between two or more splits there is no gap, an error will be +If there are multiple splits but there between two or more splits there is no gap, an error will be raised. For example:: >>> sg.update('Task', 2088, {'splits':[{'start':'2012-12-10', 'end':'2012-12-11'}, {'start':'2012-12-12', 'end':'2012-12-14'}, {'start':'2012-12-19', 'end':'2012-12-20'}]}) @@ -40,7 +40,7 @@ raised. For example:: shotgun_api3.shotgun.Fault: API update() CRUD ERROR #5: Update failed for [Task.splits]: (task.rb) The start date in split segment 2 is only one calendar day away from the end date of the previous segment. There must be calendar days between split segments. Alternately, a split value can be set to ``None`` to remove splits (you can also use an empty list). -This will preserve the ``start_date`` and ``due_date`` values but recalculate the ``duration`` value +This will preserve the ``start_date`` and ``due_date`` values but recalculate the ``duration`` value while appropriately considering all workday rules in effect. ******************************************************** @@ -50,16 +50,16 @@ How Do Splits Influence Dates And Dates Influence Splits - If splits are specified the supplied ``start_date``, ``due_date`` and ``duration`` fields will be ignored. - The ``start_date`` will be inferred from the earliest split. - The ``due_date`` will be inferred from the last split. -- If the ``start_date`` is changed on a task that has splits the first split will be moved to start - on the new ``start_date`` and all further splits will be moved while maintaining gap lengths +- If the ``start_date`` is changed on a task that has splits the first split will be moved to start + on the new ``start_date`` and all further splits will be moved while maintaining gap lengths between splits and respecting workday rules. -- If the ``due_date`` is changed on a task that has splits the last split will be moved to end on - the new ``due_date`` and all prior splits will be moved while maintaining gap lengths between +- If the ``due_date`` is changed on a task that has splits the last split will be moved to end on + the new ``due_date`` and all prior splits will be moved while maintaining gap lengths between splits and respecting workday rules. - If the ``duration`` is changed two scenarios are possible - + - In the case of a longer duration, additional days will be added to the end of the last split - - In the case of a shorter duration splits, starting with the latest ones, will be either + - In the case of a shorter duration splits, starting with the latest ones, will be either removed or shortened until the new duration is met. Examples @@ -216,7 +216,7 @@ Result: Setting the due_date in a gap ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -When a due date is set in a gap later splits are removed and the day of the due date is considered +When a due date is set in a gap later splits are removed and the day of the due date is considered a day when work will be done. For this example let's assume as a starting point the result of the 5th example: @@ -242,16 +242,3 @@ For this example let's assume as a starting point the result of the 5th example: Result: .. image:: /images/split_tasks_9.png - - - - - - - - - - - - - diff --git a/docs/cookbook/tasks/task_dependencies.rst b/docs/cookbook/tasks/task_dependencies.rst index a5cfc20be..bf6f5e610 100644 --- a/docs/cookbook/tasks/task_dependencies.rst +++ b/docs/cookbook/tasks/task_dependencies.rst @@ -4,16 +4,16 @@ Task Dependencies ################# -Task dependencies work the same way in the API as they do in the UI. You can filter and sort on +Task dependencies work the same way in the API as they do in the UI. You can filter and sort on any of the fields. For information about Task Dependencies in Flow Production Tracking, check out the `main -documentation page on our support site +documentation page on our support site `_ ************ Create Tasks ************ -Let's create a couple of Tasks and create dependencies between them. First we'll create a "Layout" +Let's create a couple of Tasks and create dependencies between them. First we'll create a "Layout" Task for our Shot:: data = { @@ -22,7 +22,7 @@ Task for our Shot:: 'start_date': '2010-04-28', 'due_date': '2010-05-05', 'entity': {'type':'Shot', 'id':860} - } + } result = sg.create(Task, data) @@ -45,7 +45,7 @@ Now let's create an "Anm" Task for our Shot:: 'start_date': '2010-05-06', 'due_date': '2010-05-12', 'entity': {'type':'Shot', 'id':860} - } + } result = sg.create(Task, data) Returns:: @@ -63,11 +63,11 @@ Returns:: Create A Dependency ******************* -Tasks each have an ``upstream_tasks`` field and a ``downstream_tasks`` field. Each field is a -list ``[]`` type and can contain zero, one, or multiple Task entity dictionaries representing the +Tasks each have an ``upstream_tasks`` field and a ``downstream_tasks`` field. Each field is a +list ``[]`` type and can contain zero, one, or multiple Task entity dictionaries representing the dependent Tasks. There are four dependency types from which you can choose: ``finish-to-start-next-day``, ``start-to-finish-next-day``, ``start-to-start``, ``finish-to-finish``. -If no dependency type is provided the default ``finish-to-start-next-day`` will be used. +If no dependency type is provided the default ``finish-to-start-next-day`` will be used. Here is how to create a dependency between our "Layout" and "Anm" Tasks:: # make 'Layout' an upstream Task to 'Anm'. (aka, make 'Anm' dependent on 'Layout') with finish-to-start-next-day dependency type @@ -85,7 +85,7 @@ Returns:: This will also automatically update the `downstream_tasks` field on 'Layout' to include the 'Anm' Task. *********************** -Query Task Dependencies +Query Task Dependencies *********************** Task Dependencies each have a ``dependent_task_id`` and a ``task_id`` fields. @@ -127,7 +127,7 @@ So now lets look at the Tasks we've created and their dependency-related fields: 'due_date', 'upstream_tasks', 'downstream_tasks', - 'dependency_violation', + 'dependency_violation', 'pinned' ] result = sg.find("Task", filters, fields) @@ -151,17 +151,17 @@ Returns:: 'pinned': False, 'start_date': '2010-05-06', 'type': 'Task', - 'upstream_tasks': [{'type': 'Task', 'name': 'Layout', 'id': 556}]}, - ... + 'upstream_tasks': [{'type': 'Task', 'name': 'Layout', 'id': 556}]}, + ... -*Note that we have also created additional Tasks for this Shot but we're going to focus on these +*Note that we have also created additional Tasks for this Shot but we're going to focus on these first two for simplicity.* ***************************************************************** Updating the End Date on a Task with Downstream Task Dependencies ***************************************************************** -If we update the ``due_date`` field on our "Layout" Task, we'll see that the "Anm" Task dates +If we update the ``due_date`` field on our "Layout" Task, we'll see that the "Anm" Task dates will automatically get pushed back to keep the dependency satisfied:: result = sg.update('Task', 556, {'due_date': '2010-05-07'}) @@ -189,20 +189,20 @@ Our Tasks now look like this (notice the new dates on the "Anm" Task):: 'pinned': False, 'start_date': '2010-05-10', 'type': 'Task', - 'upstream_tasks': [{'type': 'Task', 'name': 'Layout', 'id': 556}]}, - ... + 'upstream_tasks': [{'type': 'Task', 'name': 'Layout', 'id': 556}]}, + ... ********************************************************** Creating a Dependency Violation by pushing up a Start Date ********************************************************** -Task Dependencies can work nicely if you are pushing out an end date for a Task as it will just -recalculate the dates for all of the dependent Tasks. But what if we push up the Start Date of our +Task Dependencies can work nicely if you are pushing out an end date for a Task as it will just +recalculate the dates for all of the dependent Tasks. But what if we push up the Start Date of our "Anm" Task to start before our "Layout" Task is scheduled to end? :: - + result = sg.update('Task', 557, {'start_date': '2010-05-06'}) Returns:: @@ -229,21 +229,21 @@ Our Tasks now look like this:: 'start_date': '2010-05-06', 'type': 'Task', 'upstream_tasks': [{'type': 'Task', 'name': 'Layout', 'id': 556}]}, - ... + ... -Because the "Anm" Task ``start_date`` depends on the ``due_date`` of the "Layout" Task, this +Because the "Anm" Task ``start_date`` depends on the ``due_date`` of the "Layout" Task, this change creates a dependency violation. The update succeeds, but Flow Production Tracking has also set the -``dependency_violation`` field to ``True`` and has also updated the ``pinned`` field to ``True``. +``dependency_violation`` field to ``True`` and has also updated the ``pinned`` field to ``True``. -The ``pinned`` field simply means that if the upstream Task(s) are moved, the "Anm" Task will no -longer get moved with it. The dependency is still there (in ``upstream_tasks``) but the Task is +The ``pinned`` field simply means that if the upstream Task(s) are moved, the "Anm" Task will no +longer get moved with it. The dependency is still there (in ``upstream_tasks``) but the Task is now "pinned" to those dates until the Dependency Violation is resolved. *********************************************************** -Resolving a Dependency Violation by updating the Start Date +Resolving a Dependency Violation by updating the Start Date *********************************************************** -We don't want that violation there. Let's revert that change so the Start Date for "Anm" is after +We don't want that violation there. Let's revert that change so the Start Date for "Anm" is after the End Date of "Layout":: result = sg.update('Task', 557, {'start_date': '2010-05-10'}) @@ -272,10 +272,10 @@ Our Tasks now look like this:: 'start_date': '2010-05-10', 'type': 'Task', 'upstream_tasks': [{'type': 'Task', 'name': 'Layout', 'id': 556}]}, - ... + ... -The ``dependency_violation`` field has now been set back to ``False`` since there is no longer -a violation. But notice that the ``pinned`` field is still ``True``. We will have to manually +The ``dependency_violation`` field has now been set back to ``False`` since there is no longer +a violation. But notice that the ``pinned`` field is still ``True``. We will have to manually update that if we want the Task to travel with its dependencies again:: result = sg.update('Task', 557, {'pinned': False}) @@ -304,19 +304,19 @@ Our Tasks now look like this:: 'start_date': '2010-05-10', 'type': 'Task', 'upstream_tasks': [{'type': 'Task', 'name': 'Layout', 'id': 556}]}, - ... + ... -Looks great. But that's an annoying manual process. What if we want to just reset a Task so that +Looks great. But that's an annoying manual process. What if we want to just reset a Task so that it automatically gets updated so that the Start Date is after its dependent Tasks? ******************************************************************* Updating the ``pinned`` field on a Task with a Dependency Violation ******************************************************************* -Let's go back a couple of steps to where our "Anm" Task had a Dependency Violation because we had -moved the Start Date up before the "Layout" Task End Date. Remember that the ``pinned`` field +Let's go back a couple of steps to where our "Anm" Task had a Dependency Violation because we had +moved the Start Date up before the "Layout" Task End Date. Remember that the ``pinned`` field was also ``True``. If we simply update the ``pinned`` field to be ``False``, Flow Production Tracking will also -automatically update the Task dates to satisfy the upstream dependencies and reset the +automatically update the Task dates to satisfy the upstream dependencies and reset the ``dependency_violation`` value to ``False``:: result = sg.update('Task', 557, {'pinned': False}) @@ -345,19 +345,19 @@ Our Tasks now look like this:: 'pinned': False, 'start_date': '2010-05-10', 'type': 'Task', - 'upstream_tasks': [{'type': 'Task', 'name': 'Layout', 'id': 556}]}, - ... + 'upstream_tasks': [{'type': 'Task', 'name': 'Layout', 'id': 556}]}, + ... Notice by updating ``pinned`` to ``False``, Flow Production Tracking also updated the ``start_date`` and -``due_date`` fields of our "Anm" Task so it will satisfy the upstream Task dependencies. And since +``due_date`` fields of our "Anm" Task so it will satisfy the upstream Task dependencies. And since that succeeded, the ``dependency_violation`` field has also been set to ``False`` ******************************************* ``dependency_violation`` field is read-only ******************************************* -The ``dependency_violation`` field is the only dependency-related field that is read-only. Trying +The ``dependency_violation`` field is the only dependency-related field that is read-only. Trying to modify it will generate a Fault:: result = sg.update('Task', 557, {'dependency_violation': False}) diff --git a/docs/cookbook/tasks/updating_tasks.rst b/docs/cookbook/tasks/updating_tasks.rst index 97eb8c7b7..c7c216e3f 100644 --- a/docs/cookbook/tasks/updating_tasks.rst +++ b/docs/cookbook/tasks/updating_tasks.rst @@ -4,9 +4,9 @@ Updating Task Dates: How Flow Production Tracking Thinks ######################################################## -When updating Task dates in an API update() request, there is no specified order to the values that +When updating Task dates in an API update() request, there is no specified order to the values that are passed in. Flow Production Tracking also does automatic calculation of the``start_date``,``due_date``, and ``duration`` fields for convenience. In order to clarify how updates are handled by Flow Production Tracking we are -providing some general rules below and examples of what will happen when you make updates to your +providing some general rules below and examples of what will happen when you make updates to your Tasks. ************** @@ -17,31 +17,31 @@ General Rules - Updating the ``due_date`` automatically updates the ``duration`` (``start_date`` remains constant) - Updating the ``duration`` automatically updates the ``due_date`` (``start_date`` remains constant) - When updating Task values, Flow Production Tracking sets schedule fields (``milestone``, ``duration``, - ``start_date``, ``due_date``) after all other fields, because the Project and Task Assignees + ``start_date``, ``due_date``) after all other fields, because the Project and Task Assignees affect schedule calculations. -- If ``start_date`` and ``due_date`` are both set, ``duration`` is ignored (``duration`` can often +- If ``start_date`` and ``due_date`` are both set, ``duration`` is ignored (``duration`` can often be wrong since it's easy to calculate scheduling incorrectly). - If both ``start_date`` and ``due_date`` are provided, Flow Production Tracking sets ``start_date`` before ``due_date``. -- Set ``milestone`` before other schedule fields (because ``start_date``, ``due_date``, and +- Set ``milestone`` before other schedule fields (because ``start_date``, ``due_date``, and ``duration`` get lost if ``milestone`` is not set to ``False`` first) - If ``milestone`` is being set to ``True``, ``duration`` is ignored. -- If ``milestone`` is set to ``True`` and ``start_date`` and ``due_date`` are also being set to +- If ``milestone`` is set to ``True`` and ``start_date`` and ``due_date`` are also being set to conflicting values, an Exception is raised. -- If ``due_date`` and ``duration`` are set together (without ``start_date``), ``duration`` is set - first, then ``due_date`` (otherwise setting ``duration`` will change ``due_date`` after it is +- If ``due_date`` and ``duration`` are set together (without ``start_date``), ``duration`` is set + first, then ``due_date`` (otherwise setting ``duration`` will change ``due_date`` after it is set). ******** Examples ******** -The following examples show what the resulting Task object will look like after being run on the +The following examples show what the resulting Task object will look like after being run on the initial Task object listed under the header of each section. The ``duration`` values in the following examples assume your Flow Production Tracking instance is set to -10-hour work days. If your server is configured with a different setting, the ``duration`` values -will vary. +10-hour work days. If your server is configured with a different setting, the ``duration`` values +will vary. .. note:: The ``duration`` field stores ``duration`` values in minutes @@ -56,7 +56,7 @@ Regardless of current values on the Task, this behavior rules:: **Update start_date and due_date** -``duration`` is ignored if also provided. It is instead set automatically as (``due_date`` - +``duration`` is ignored if also provided. It is instead set automatically as (``due_date`` - ``start_date``) :: @@ -66,7 +66,7 @@ Regardless of current values on the Task, this behavior rules:: - ``start_date`` is updated. - ``due_date`` is updated. -- ``duration`` is calculated as (``due_date`` - ``start_date``) +- ``duration`` is calculated as (``due_date`` - ``start_date``) .. note:: The value provided in the update() is ignored (and in this case was also incorrect). @@ -90,7 +90,7 @@ Regardless of current values on the Task, this behavior rules:: - ``duration`` is updated. - ``due_date`` is updated. -- ``duration`` is calculated as (``due_date`` - ``start_date``) +- ``duration`` is calculated as (``due_date`` - ``start_date``) .. note:: This means the ``duration`` provided is overwritten. @@ -226,7 +226,7 @@ If the Task has ``start_date`` and ``due_date`` values but has no ``duration``, will behave. :: - + # Task = {'start_date': '2011-05-20', 'due_date': '2011-05-25', 'duration': None, 'id':123} **Update start_date** @@ -310,7 +310,7 @@ If the Task has ``due_date`` and ``duration`` values but has no ``start_date``, will behave. :: - + # Task = {'start_date': None, 'due_date': '2011-05-25', 'duration': 2400, 'id':123} **Update start_date** @@ -383,4 +383,4 @@ will behave. # Task = {'start_date': '2011-05-20', 'due_date': '2011-05-27', 'duration': 3600, 'id':123} - ``duration`` is updated. -- ``due_date`` is updated to (``start_date`` + ``duration``) \ No newline at end of file +- ``due_date`` is updated to (``start_date`` + ``duration``) diff --git a/docs/cookbook/tutorials.rst b/docs/cookbook/tutorials.rst index 99f56da02..2adbd4fbf 100644 --- a/docs/cookbook/tutorials.rst +++ b/docs/cookbook/tutorials.rst @@ -2,7 +2,7 @@ Examples ######## -Here's a list of various simple tutorials to walk through that should provide you with a good base +Here's a list of various simple tutorials to walk through that should provide you with a good base understanding of how to use the Flow Production Tracking API and what you can do with it. ***** diff --git a/docs/cookbook/usage_tips.rst b/docs/cookbook/usage_tips.rst index 91cd6e8cb..5d1a7bc1f 100644 --- a/docs/cookbook/usage_tips.rst +++ b/docs/cookbook/usage_tips.rst @@ -3,9 +3,9 @@ API Usage Tips ############## Below is a list of helpful tips when using the Flow Production Tracking API3. We have tried to make the API very -simple to use with predictable results while remaining a powerful tool to integrate with your -pipeline. However, there's always a couple of things that crop up that our users might not be -aware of. Those are the types of things you'll find below. We'll be adding to this document over +simple to use with predictable results while remaining a powerful tool to integrate with your +pipeline. However, there's always a couple of things that crop up that our users might not be +aware of. Those are the types of things you'll find below. We'll be adding to this document over time as new questions come up from our users that exhibit these types of cases. ********* @@ -43,13 +43,13 @@ the entities are returned in a standard dictionary:: {'type': 'Asset', 'name': 'redBall', 'id': 1} -For each entity returned, you will get a ``type``, ``name``, and ``id`` key. This does not mean -there are fields named ``type`` and ``name`` on the Asset. These are only used to provide a +For each entity returned, you will get a ``type``, ``name``, and ``id`` key. This does not mean +there are fields named ``type`` and ``name`` on the Asset. These are only used to provide a consistent way to represent entities returned via the API. - ``type``: the entity type (CamelCase) - ``name``: the display name of the entity. For most entity types this is the value of the ``code`` - field but not always. For example, on the Ticket and Delivery entities the ``name`` key would + field but not always. For example, on the Ticket and Delivery entities the ``name`` key would contain the value of the ``title`` field. .. _custom_entities: @@ -100,14 +100,14 @@ Connection entities exist behind the scenes for any many-to-many relationship. M you won't need to pay any attention to them. But in some cases, you may need to track information on the instance of one entity's relationship to another. -For example, when viewing a list of Versions on a Playlist, the Sort Order (``sg_sort_order``) field is an +For example, when viewing a list of Versions on a Playlist, the Sort Order (``sg_sort_order``) field is an example of a field that resides on the connection entity between Playlists and Versions. This -connection entity is appropriately called `PlaylistVersionConnection`. Because any Version can -exist in multiple Playlists, the sort order isn't specific to the Version, it's specific to -each _instance_ of the Version in a Playlist. These instances are tracked using connection +connection entity is appropriately called `PlaylistVersionConnection`. Because any Version can +exist in multiple Playlists, the sort order isn't specific to the Version, it's specific to +each _instance_ of the Version in a Playlist. These instances are tracked using connection entities in Shtogun and are accessible just like any other entity type in Flow Production Tracking. -To find information about your Versions in the Playlist "Director Review" (let's say it has an +To find information about your Versions in the Playlist "Director Review" (let's say it has an ``id`` of 4). We'd run a query like so:: filters = [['playlist', 'is', {'type':'Playlist', 'id':4}]] @@ -169,9 +169,9 @@ Which returns the following:: - ``playlist`` is the Playlist record for this connection instance. - ``sg_sort_order`` is the sort order field on the connection instance. -We can pull in field values from the linked Playlist and Version entities using dot notation like -``version.Version.code``. The syntax is ``fieldname.EntityType.fieldname``. In this example, -``PlaylistVersionConnection`` has a field named ``version``. That field contains a ``Version`` +We can pull in field values from the linked Playlist and Version entities using dot notation like +``version.Version.code``. The syntax is ``fieldname.EntityType.fieldname``. In this example, +``PlaylistVersionConnection`` has a field named ``version``. That field contains a ``Version`` entity. The field we are interested on the Version is ``code``. Put those together with our f riend the dot and we have ``version.Version.code``. @@ -179,20 +179,20 @@ riend the dot and we have ``version.Version.code``. Flow Production Tracking UI fields not available via the API ************************************************************ -Summary type fields like Query Fields and Pipeline Step summary fields are currently only available -via the UI. Some other fields may not work as expected through the API because they are "display +Summary type fields like Query Fields and Pipeline Step summary fields are currently only available +via the UI. Some other fields may not work as expected through the API because they are "display only" fields made available for convenience and are only available in the browser UI. HumanUser ========= -- ``name``: This is a UI-only field that is a combination of the ``firstname`` + ``' '`` + +- ``name``: This is a UI-only field that is a combination of the ``firstname`` + ``' '`` + ``lastname``. Shot ==== -**Smart Cut Fields**: These fields are available only in the browser UI. You can read more about +**Smart Cut Fields**: These fields are available only in the browser UI. You can read more about smart cut fields and the API in the :ref:`Smart Cut Fields doc `:: smart_cut_in @@ -212,25 +212,25 @@ smart cut fields and the API in the :ref:`Smart Cut Fields doc `_. This allows you to write plug-ins that watch for certain types of events and then run code when they occur. - + Structure of Event Types ======================== @@ -872,57 +872,57 @@ The basic structure of event types is broken into 3 parts: - ``Application``: Is always "Shotgun" for events automatically created by the Flow Production Tracking server. Other Flow Production Tracking products may use their name in here, for example, Toolkit has its own events - that it logs and the application portion is identified by "Toolkit". If you decide to use the + that it logs and the application portion is identified by "Toolkit". If you decide to use the EventLogEntry entity to log events for your scripts or tools, you would use your tool name here. - ``EntityType``: This is the entity type in Flow Production Tracking that was acted upon (eg. Shot, Asset, etc.) -- ``Action``: The general action that was taken. (eg. New, Change, Retirement, Revival) - +- ``Action``: The general action that was taken. (eg. New, Change, Retirement, Revival) + Standard Event Types ==================== -Each entity type has a standard set of events associated with it when it's created, updated, +Each entity type has a standard set of events associated with it when it's created, updated, deleted, and revived. They follow this pattern: - ``Shotgun_EntityType_New``: a new entity was created. Example: ``Shotgun_Task_New`` - ``Shotgun_EntityType_Change``: an entity was modified. Example: ``Shotgun_HumanUser_Change`` - ``Shotgun_EntityType_Retirement``: an entity was deleted. Example: ``Shotgun_Ticket_Retirement`` -- ``Shotgun_EntityType_Revival``: an entity was revived. Example: ``Shotgun_CustomEntity03_Revival`` +- ``Shotgun_EntityType_Revival``: an entity was revived. Example: ``Shotgun_CustomEntity03_Revival`` Additional Event Types ====================== These are _some_ of the additional event types that are logged by Flow Production Tracking: - + - ``Shotgun_Attachment_View``: an Attachment (file) was viewed by a user. -- ``Shotgun_Reading_Change``: a threaded entity has been marked read or unread. For example, a - Note was read by a user. The readings are unique to the entity<->user connection so when a +- ``Shotgun_Reading_Change``: a threaded entity has been marked read or unread. For example, a + Note was read by a user. The readings are unique to the entity<->user connection so when a Note is read by user "joe" it may still be unread by user "jane". - ``Shotgun_User_Login``: a user logged in to Flow Production Tracking. - ``Shotgun_User_Logout``: a user logged out of Flow Production Tracking. - + Custom Event Types ================== -Since ``EventLogEntries`` are entities themselves, you can create them using the API just like any -other entity type. As mentioned previously, if you'd like to have your scripts or tools log to +Since ``EventLogEntries`` are entities themselves, you can create them using the API just like any +other entity type. As mentioned previously, if you'd like to have your scripts or tools log to the Flow Production Tracking event log, simply devise a thoughtful naming structure for your event types and create the EventLogEntry as needed following the usual methods for creating entities via the API. Again, other Flow Production Tracking products like Toolkit use event logs this way. -.. note:: - EventLogEntries cannot be updated or deleted (that would defeat the purpose of course). - +.. note:: + EventLogEntries cannot be updated or deleted (that would defeat the purpose of course). + Performance =========== Event log database tables can get large very quickly. While Flow Production Tracking does very well with event logs -that get into the millions of records, there's an inevitable degradation of performance for pages -that display them in the web application as well as any API queries for events when they get too -big. This volume of events is not the norm, but can be reached if your server expereinces high -usage. +that get into the millions of records, there's an inevitable degradation of performance for pages +that display them in the web application as well as any API queries for events when they get too +big. This volume of events is not the norm, but can be reached if your server expereinces high +usage. This **does not** mean your Flow Production Tracking server performance will suffer in general, just any pages that are specifically displaying EventLogEntries in the web application, or API queries on the event @@ -976,7 +976,7 @@ Will internally be transformed as if you invoked something like this: .. code-block:: python - sg.find('Asset', [['project', 'is', {'id': 999, 'type': 'Project'}]]) + sg.find('Asset', [['project', 'is', {'id': 999, 'type': 'Project'}]]) ************ diff --git a/nose.cfg b/nose.cfg index 59c3f0974..22c0e11cd 100644 --- a/nose.cfg +++ b/nose.cfg @@ -9,4 +9,4 @@ # not expressly granted therein are reserved by Shotgun Software Inc. [nosetests] -exclude-dir=shotgun_api3/lib \ No newline at end of file +exclude-dir=shotgun_api3/lib diff --git a/run-tests b/run-tests index dbe93f8b2..61b0f82c9 100755 --- a/run-tests +++ b/run-tests @@ -1,3 +1,5 @@ +#!/usr/bin/env bash + # Copyright (c) 2019 Shotgun Software Inc. # # CONFIDENTIAL AND PROPRIETARY diff --git a/setup.py b/setup.py index 337e3b13b..f92018fe1 100644 --- a/setup.py +++ b/setup.py @@ -12,25 +12,25 @@ import sys from setuptools import setup, find_packages -f = open('README.md') +f = open("README.md") readme = f.read().strip() -f = open('LICENSE') +f = open("LICENSE") license = f.read().strip() setup( - name='shotgun_api3', - version='3.8.0', - description='Flow Production Tracking Python API', + name="shotgun_api3", + version="3.8.0", + description="Flow Production Tracking Python API", long_description=readme, - author='Autodesk', - author_email='https://www.autodesk.com/support/contact-support', - url='https://github.com/shotgunsoftware/python-api', + author="Autodesk", + author_email="https://www.autodesk.com/support/contact-support", + url="https://github.com/shotgunsoftware/python-api", license=license, - packages=find_packages(exclude=('tests',)), + packages=find_packages(exclude=("tests",)), script_args=sys.argv[1:], include_package_data=True, - package_data={'': ['cacerts.txt', 'cacert.pem']}, + package_data={"": ["cacerts.txt", "cacert.pem"]}, zip_safe=False, python_requires=">=3.7.0", classifiers=[ diff --git a/shotgun_api3/__init__.py b/shotgun_api3/__init__.py index 49e96db7a..d296aa97a 100644 --- a/shotgun_api3/__init__.py +++ b/shotgun_api3/__init__.py @@ -8,9 +8,18 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. -from .shotgun import (Shotgun, ShotgunError, ShotgunFileDownloadError, # noqa unused imports - ShotgunThumbnailNotReady, Fault, - AuthenticationFault, MissingTwoFactorAuthenticationFault, - UserCredentialsNotAllowedForSSOAuthenticationFault, - ProtocolError, ResponseError, Error, __version__) -from .shotgun import SG_TIMEZONE as sg_timezone # noqa unused imports +from .shotgun import ( + Shotgun, + ShotgunError, + ShotgunFileDownloadError, # noqa unused imports + ShotgunThumbnailNotReady, + Fault, + AuthenticationFault, + MissingTwoFactorAuthenticationFault, + UserCredentialsNotAllowedForSSOAuthenticationFault, + ProtocolError, + ResponseError, + Error, + __version__, +) +from .shotgun import SG_TIMEZONE as sg_timezone # noqa unused imports diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index f0d4faf48..a805fa5f4 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -1,56 +1,56 @@ #!/usr/bin/env python """ - ----------------------------------------------------------------------------- - Copyright (c) 2009-2019, Shotgun Software Inc. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - - Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - - - Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - - Neither the name of the Shotgun Software Inc nor the names of its - contributors may be used to endorse or promote products derived from this - software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +Copyright (c) 2009-2019, Shotgun Software Inc. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + - Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + - Neither the name of the Shotgun Software Inc nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ # Python 2/3 compatibility from .lib import six from .lib import sgsix from .lib import sgutils -from .lib.six import BytesIO # used for attachment upload +from .lib.six import BytesIO # used for attachment upload from .lib.six.moves import map from .lib.six.moves import http_cookiejar # used for attachment upload import datetime import logging -import uuid # used for attachment upload +import uuid # used for attachment upload import os import re import copy import ssl -import stat # used for attachment upload +import stat # used for attachment upload import sys import time import json from .lib.six.moves import urllib -import shutil # used for attachment download -from .lib.six.moves import http_client # Used for secure file upload. +import shutil # used for attachment download +from .lib.six.moves import http_client # Used for secure file upload. from .lib.httplib2 import Http, ProxyInfo, socks, ssl_error_classes from .lib.sgtimezone import SgTimezone @@ -88,9 +88,13 @@ def _is_mimetypes_broken(): # http://bugs.python.org/issue9291 <- Fixed in 2.7.7 # http://bugs.python.org/issue21652 <- Fixed in 2.7.8 # http://bugs.python.org/issue22028 <- Fixed in 2.7.10 - return (sys.platform == "win32" and - sys.version_info[0] == 2 and sys.version_info[1] == 7 and - sys.version_info[2] >= 0 and sys.version_info[2] <= 9) + return ( + sys.platform == "win32" + and sys.version_info[0] == 2 + and sys.version_info[1] == 7 + and sys.version_info[2] >= 0 + and sys.version_info[2] <= 9 + ) if _is_mimetypes_broken(): @@ -101,7 +105,7 @@ def _is_mimetypes_broken(): # mimetypes imported in version specific imports mimetypes.add_type("video/webm", ".webm") # webm and mp4 seem to be missing -mimetypes.add_type("video/mp4", ".mp4") # from some OS/distros +mimetypes.add_type("video/mp4", ".mp4") # from some OS/distros SG_TIMEZONE = SgTimezone() @@ -128,6 +132,7 @@ class ShotgunError(Exception): """ Base for all Shotgun API Errors. """ + pass @@ -135,6 +140,7 @@ class ShotgunFileDownloadError(ShotgunError): """ Exception for file download-related errors. """ + pass @@ -142,6 +148,7 @@ class ShotgunThumbnailNotReady(ShotgunError): """ Exception for when trying to use a 'pending thumbnail' (aka transient thumbnail) in an operation """ + pass @@ -149,6 +156,7 @@ class Fault(ShotgunError): """ Exception when server-side exception detected. """ + pass @@ -156,6 +164,7 @@ class AuthenticationFault(Fault): """ Exception when the server side reports an error related to authentication. """ + pass @@ -164,6 +173,7 @@ class MissingTwoFactorAuthenticationFault(Fault): Exception when the server side reports an error related to missing two-factor authentication credentials. """ + pass @@ -172,6 +182,7 @@ class UserCredentialsNotAllowedForSSOAuthenticationFault(Fault): Exception when the server is configured to use SSO. It is not possible to use a username/password pair to authenticate on such server. """ + pass @@ -180,8 +191,10 @@ class UserCredentialsNotAllowedForOxygenAuthenticationFault(Fault): Exception when the server is configured to use Oxygen. It is not possible to use a username/password pair to authenticate on such server. """ + pass + # ---------------------------------------------------------------------------- # API @@ -221,10 +234,12 @@ def __init__(self, host, meta): except AttributeError: self.version = None if not self.version: - raise ShotgunError("The Flow Production Tracking Server didn't respond with a version number. " - "This may be because you are running an older version of " - "Flow Production Tracking against a more recent version of the Flow Production Tracking API. " - "For more information, please contact the Autodesk support.") + raise ShotgunError( + "The Flow Production Tracking Server didn't respond with a version number. " + "This may be because you are running an older version of " + "Flow Production Tracking against a more recent version of the Flow Production Tracking API. " + "For more information, please contact the Autodesk support." + ) if len(self.version) > 3 and self.version[3] == "Dev": self.is_dev = True @@ -258,7 +273,12 @@ def _ensure_support(self, feature, raise_hell=True): if raise_hell: raise ShotgunError( "%s requires server version %s or higher, " - "server is %s" % (feature["label"], _version_str(feature["version"]), _version_str(self.version)) + "server is %s" + % ( + feature["label"], + _version_str(feature["version"]), + _version_str(self.version), + ) ) return False else: @@ -268,68 +288,62 @@ def _ensure_json_supported(self): """ Ensures server has support for JSON API endpoint added in v2.4.0. """ - self._ensure_support({ - "version": (2, 4, 0), - "label": "JSON API" - }) + self._ensure_support({"version": (2, 4, 0), "label": "JSON API"}) def ensure_include_archived_projects(self): """ Ensures server has support for archived Projects feature added in v5.3.14. """ - self._ensure_support({ - "version": (5, 3, 14), - "label": "include_archived_projects parameter" - }) + self._ensure_support( + {"version": (5, 3, 14), "label": "include_archived_projects parameter"} + ) def ensure_per_project_customization(self): """ Ensures server has support for per-project customization feature added in v5.4.4. """ - return self._ensure_support({ - "version": (5, 4, 4), - "label": "project parameter" - }, True) + return self._ensure_support( + {"version": (5, 4, 4), "label": "project parameter"}, True + ) def ensure_support_for_additional_filter_presets(self): """ Ensures server has support for additional filter presets feature added in v7.0.0. """ - return self._ensure_support({ - "version": (7, 0, 0), - "label": "additional_filter_presets parameter" - }, True) + return self._ensure_support( + {"version": (7, 0, 0), "label": "additional_filter_presets parameter"}, True + ) def ensure_user_following_support(self): """ Ensures server has support for listing items a user is following, added in v7.0.12. """ - return self._ensure_support({ - "version": (7, 0, 12), - "label": "user_following parameter" - }, True) + return self._ensure_support( + {"version": (7, 0, 12), "label": "user_following parameter"}, True + ) def ensure_paging_info_without_counts_support(self): """ Ensures server has support for optimized pagination, added in v7.4.0. """ - return self._ensure_support({ - "version": (7, 4, 0), - "label": "optimized pagination" - }, False) + return self._ensure_support( + {"version": (7, 4, 0), "label": "optimized pagination"}, False + ) def ensure_return_image_urls_support(self): """ Ensures server has support for returning thumbnail URLs without additional round-trips, added in v3.3.0. """ - return self._ensure_support({ - "version": (3, 3, 0), - "label": "return thumbnail URLs" - }, False) + return self._ensure_support( + {"version": (3, 3, 0), "label": "return thumbnail URLs"}, False + ) def __str__(self): - return "ServerCapabilities: host %s, version %s, is_dev %s"\ - % (self.host, self.version, self.is_dev) + return "ServerCapabilities: host %s, version %s, is_dev %s" % ( + self.host, + self.version, + self.is_dev, + ) class ClientCapabilities(object): @@ -379,9 +393,11 @@ def __init__(self): pass def __str__(self): - return "ClientCapabilities: platform %s, local_path_field %s, "\ - "py_verison %s, ssl version %s" % (self.platform, self.local_path_field, - self.py_version, self.ssl_version) + return ( + "ClientCapabilities: platform %s, local_path_field %s, " + "py_verison %s, ssl version %s" + % (self.platform, self.local_path_field, self.py_version, self.ssl_version) + ) class _Config(object): @@ -459,14 +475,11 @@ def set_server_params(self, base_url): :raises ValueError: Raised if protocol is not http or https. """ - self.scheme, self.server, api_base, _, _ = \ - urllib.parse.urlsplit(base_url) + self.scheme, self.server, api_base, _, _ = urllib.parse.urlsplit(base_url) if self.scheme not in ("http", "https"): - raise ValueError( - "base_url must use http or https got '%s'" % base_url - ) - self.api_path = urllib.parse.urljoin(urllib.parse.urljoin( - api_base or "/", self.api_ver + "/"), "json" + raise ValueError("base_url must use http or https got '%s'" % base_url) + self.api_path = urllib.parse.urljoin( + urllib.parse.urljoin(api_base or "/", self.api_ver + "/"), "json" ) @property @@ -477,7 +490,9 @@ def records_per_page(self): if self._records_per_page is None: # Check for api_max_entities_per_page in the server info and change the record per page # value if it is supplied. - self._records_per_page = self._sg.server_info.get("api_max_entities_per_page") or 500 + self._records_per_page = ( + self._sg.server_info.get("api_max_entities_per_page") or 500 + ) return self._records_per_page @@ -489,30 +504,32 @@ class Shotgun(object): # reg ex from # http://underground.infovark.com/2008/07/22/iso-date-validation-regex/ # Note a length check is done before checking the reg ex - _DATE_PATTERN = re.compile( - r"^(\d{4})\D?(0[1-9]|1[0-2])\D?([12]\d|0[1-9]|3[01])$") + _DATE_PATTERN = re.compile(r"^(\d{4})\D?(0[1-9]|1[0-2])\D?([12]\d|0[1-9]|3[01])$") _DATE_TIME_PATTERN = re.compile( r"^(\d{4})\D?(0[1-9]|1[0-2])\D?([12]\d|0[1-9]|3[01])" - r"(\D?([01]\d|2[0-3])\D?([0-5]\d)\D?([0-5]\d)?\D?(\d{3})?)?$") + r"(\D?([01]\d|2[0-3])\D?([0-5]\d)\D?([0-5]\d)?\D?(\d{3})?)?$" + ) _MULTIPART_UPLOAD_CHUNK_SIZE = 20000000 - MAX_ATTEMPTS = 3 # Retries on failure - BACKOFF = 0.75 # Seconds to wait before retry, times the attempt number - - def __init__(self, - base_url, - script_name=None, - api_key=None, - convert_datetimes_to_utc=True, - http_proxy=None, - ensure_ascii=True, - connect=True, - ca_certs=None, - login=None, - password=None, - sudo_as_login=None, - session_token=None, - auth_token=None): + MAX_ATTEMPTS = 3 # Retries on failure + BACKOFF = 0.75 # Seconds to wait before retry, times the attempt number + + def __init__( + self, + base_url, + script_name=None, + api_key=None, + convert_datetimes_to_utc=True, + http_proxy=None, + ensure_ascii=True, + connect=True, + ca_certs=None, + login=None, + password=None, + sudo_as_login=None, + session_token=None, + auth_token=None, + ): """ Initializes a new instance of the Shotgun client. @@ -597,16 +614,19 @@ def __init__(self, # verify authentication arguments if session_token is not None: if script_name is not None or api_key is not None: - raise ValueError("cannot provide both session_token " - "and script_name/api_key") + raise ValueError( + "cannot provide both session_token " "and script_name/api_key" + ) if login is not None or password is not None: - raise ValueError("cannot provide both session_token " - "and login/password") + raise ValueError( + "cannot provide both session_token " "and login/password" + ) if login is not None or password is not None: if script_name is not None or api_key is not None: - raise ValueError("cannot provide both login/password " - "and script_name/api_key") + raise ValueError( + "cannot provide both login/password " "and script_name/api_key" + ) if login is None: raise ValueError("password provided without login") if password is None: @@ -620,15 +640,24 @@ def __init__(self, if auth_token is not None: if login is None or password is None: - raise ValueError("must provide a user login and password with an auth_token") + raise ValueError( + "must provide a user login and password with an auth_token" + ) if script_name is not None or api_key is not None: raise ValueError("cannot provide an auth_code with script_name/api_key") # Can't use 'all' with python 2.4 - if len([x for x in [session_token, script_name, api_key, login, password] if x]) == 0: + if ( + len( + [x for x in [session_token, script_name, api_key, login, password] if x] + ) + == 0 + ): if connect: - raise ValueError("must provide login/password, session_token or script_name/api_key") + raise ValueError( + "must provide login/password, session_token or script_name/api_key" + ) self.config = _Config(self) self.config.api_key = api_key @@ -643,19 +672,30 @@ def __init__(self, self.config.raw_http_proxy = http_proxy try: - self.config.rpc_attempt_interval = int(os.environ.get("SHOTGUN_API_RETRY_INTERVAL", 3000)) + self.config.rpc_attempt_interval = int( + os.environ.get("SHOTGUN_API_RETRY_INTERVAL", 3000) + ) except ValueError: retry_interval = os.environ.get("SHOTGUN_API_RETRY_INTERVAL", 3000) - raise ValueError("Invalid value '%s' found in environment variable " - "SHOTGUN_API_RETRY_INTERVAL, must be int." % retry_interval) + raise ValueError( + "Invalid value '%s' found in environment variable " + "SHOTGUN_API_RETRY_INTERVAL, must be int." % retry_interval + ) if self.config.rpc_attempt_interval < 0: - raise ValueError("Value of SHOTGUN_API_RETRY_INTERVAL must be positive, " - "got '%s'." % self.config.rpc_attempt_interval) - + raise ValueError( + "Value of SHOTGUN_API_RETRY_INTERVAL must be positive, " + "got '%s'." % self.config.rpc_attempt_interval + ) + global SHOTGUN_API_DISABLE_ENTITY_OPTIMIZATION - if os.environ.get("SHOTGUN_API_DISABLE_ENTITY_OPTIMIZATION", "0").strip().lower() == "1": + if ( + os.environ.get("SHOTGUN_API_DISABLE_ENTITY_OPTIMIZATION", "0") + .strip() + .lower() + == "1" + ): SHOTGUN_API_DISABLE_ENTITY_OPTIMIZATION = True - + self._connection = None self.__ca_certs = self._get_certs_file(ca_certs) @@ -672,8 +712,9 @@ def __init__(self, # the lowercase version of the credentials. auth, self.config.server = self._split_url(base_url) if auth: - auth = base64encode(sgutils.ensure_binary( - urllib.parse.unquote(auth))).decode("utf-8") + auth = base64encode( + sgutils.ensure_binary(urllib.parse.unquote(auth)) + ).decode("utf-8") self.config.authorization = "Basic " + auth.strip() # foo:bar@123.456.789.012:3456 @@ -682,8 +723,7 @@ def __init__(self, # there might be @ in the user's password. p = http_proxy.rsplit("@", 1) if len(p) > 1: - self.config.proxy_user, self.config.proxy_pass = \ - p[0].split(":", 1) + self.config.proxy_user, self.config.proxy_pass = p[0].split(":", 1) proxy_server = p[1] else: proxy_server = http_proxy @@ -693,18 +733,29 @@ def __init__(self, try: self.config.proxy_port = int(proxy_netloc_list[1]) except ValueError: - raise ValueError("Invalid http_proxy address '%s'. Valid " - "format is '123.456.789.012' or '123.456.789.012:3456'" - ". If no port is specified, a default of %d will be " - "used." % (http_proxy, self.config.proxy_port)) + raise ValueError( + "Invalid http_proxy address '%s'. Valid " + "format is '123.456.789.012' or '123.456.789.012:3456'" + ". If no port is specified, a default of %d will be " + "used." % (http_proxy, self.config.proxy_port) + ) # now populate self.config.proxy_handler if self.config.proxy_user and self.config.proxy_pass: - auth_string = "%s:%s@" % (self.config.proxy_user, self.config.proxy_pass) + auth_string = "%s:%s@" % ( + self.config.proxy_user, + self.config.proxy_pass, + ) else: auth_string = "" - proxy_addr = "http://%s%s:%d" % (auth_string, self.config.proxy_server, self.config.proxy_port) - self.config.proxy_handler = urllib.request.ProxyHandler({self.config.scheme: proxy_addr}) + proxy_addr = "http://%s%s:%d" % ( + auth_string, + self.config.proxy_server, + self.config.proxy_port, + ) + self.config.proxy_handler = urllib.request.ProxyHandler( + {self.config.scheme: proxy_addr} + ) if ensure_ascii: self._json_loads = self._json_loads_ascii @@ -750,7 +801,8 @@ def _split_url(self, base_url): else: auth, server = urllib.parse.splituser( - urllib.parse.urlsplit(base_url).netloc) + urllib.parse.urlsplit(base_url).netloc + ) return auth, server @@ -842,8 +894,17 @@ def info(self): """ return self._call_rpc("info", None, include_auth_params=False) - def find_one(self, entity_type, filters, fields=None, order=None, filter_operator=None, retired_only=False, - include_archived_projects=True, additional_filter_presets=None): + def find_one( + self, + entity_type, + filters, + fields=None, + order=None, + filter_operator=None, + retired_only=False, + include_archived_projects=True, + additional_filter_presets=None, + ): """ Shortcut for :meth:`~shotgun_api3.Shotgun.find` with ``limit=1`` so it returns a single result. @@ -897,16 +958,35 @@ def find_one(self, entity_type, filters, fields=None, order=None, filter_operato :rtype: dict """ - results = self.find(entity_type, filters, fields, order, filter_operator, 1, retired_only, - include_archived_projects=include_archived_projects, - additional_filter_presets=additional_filter_presets) + results = self.find( + entity_type, + filters, + fields, + order, + filter_operator, + 1, + retired_only, + include_archived_projects=include_archived_projects, + additional_filter_presets=additional_filter_presets, + ) if results: return results[0] return None - def find(self, entity_type, filters, fields=None, order=None, filter_operator=None, limit=0, - retired_only=False, page=0, include_archived_projects=True, additional_filter_presets=None): + def find( + self, + entity_type, + filters, + fields=None, + order=None, + filter_operator=None, + limit=0, + retired_only=False, + page=0, + include_archived_projects=True, + additional_filter_presets=None, + ): """ Find entities matching the given filters. @@ -959,7 +1039,7 @@ def find(self, entity_type, filters, fields=None, order=None, filter_operator=No Defaults to ``["id"]``. .. seealso:: :ref:`combining-related-queries` - + :param list order: Optional list of dictionaries defining how to order the results of the query. Each dictionary contains the ``field_name`` to order by and the ``direction`` to sort:: @@ -1016,8 +1096,10 @@ def find(self, entity_type, filters, fields=None, order=None, filter_operator=No filters = _translate_filters(filters, filter_operator) elif filter_operator: # TODO: Not sure if this test is correct, replicated from prev api - raise ShotgunError("Deprecated: Use of filter_operator for find() is not valid any more." - " See the documentation on find()") + raise ShotgunError( + "Deprecated: Use of filter_operator for find() is not valid any more." + " See the documentation on find()" + ) if not include_archived_projects: # This defaults to True on the server (no argument is sent) @@ -1027,13 +1109,15 @@ def find(self, entity_type, filters, fields=None, order=None, filter_operator=No if additional_filter_presets: self.server_caps.ensure_support_for_additional_filter_presets() - params = self._construct_read_parameters(entity_type, - fields, - filters, - retired_only, - order, - include_archived_projects, - additional_filter_presets) + params = self._construct_read_parameters( + entity_type, + fields, + filters, + retired_only, + order, + include_archived_projects, + additional_filter_presets, + ) if self.server_caps.ensure_return_image_urls_support(): params["api_return_image_urls"] = True @@ -1089,21 +1173,25 @@ def find(self, entity_type, filters, fields=None, order=None, filter_operator=No return self._parse_records(records) - def _construct_read_parameters(self, - entity_type, - fields, - filters, - retired_only, - order, - include_archived_projects, - additional_filter_presets): + def _construct_read_parameters( + self, + entity_type, + fields, + filters, + retired_only, + order, + include_archived_projects, + additional_filter_presets, + ): params = {} params["type"] = entity_type params["return_fields"] = fields or ["id"] params["filters"] = filters params["return_only"] = (retired_only and "retired") or "active" - params["paging"] = {"entities_per_page": self.config.records_per_page, - "current_page": 1} + params["paging"] = { + "entities_per_page": self.config.records_per_page, + "current_page": 1, + } if additional_filter_presets: params["additional_filter_presets"] = additional_filter_presets @@ -1119,10 +1207,9 @@ def _construct_read_parameters(self, # TODO: warn about deprecation of 'column' param name sort["field_name"] = sort["column"] sort.setdefault("direction", "asc") - sort_list.append({ - "field_name": sort["field_name"], - "direction": sort["direction"] - }) + sort_list.append( + {"field_name": sort["field_name"], "direction": sort["direction"]} + ) params["sorts"] = sort_list return params @@ -1132,7 +1219,7 @@ def _add_project_param(self, params, project_entity): params["project"] = project_entity return params - + def _translate_update_params( self, entity_type, entity_id, data, multi_entity_update_modes ): @@ -1155,13 +1242,15 @@ def optimize_field(field_dict): "fields": [optimize_field(field_dict) for field_dict in full_fields], } - def summarize(self, - entity_type, - filters, - summary_fields, - filter_operator=None, - grouping=None, - include_archived_projects=True): + def summarize( + self, + entity_type, + filters, + summary_fields, + filter_operator=None, + grouping=None, + include_archived_projects=True, + ): """ Summarize field data returned by a query. @@ -1349,9 +1438,7 @@ def summarize(self, # So we only need to check the server version if it is False self.server_caps.ensure_include_archived_projects() - params = {"type": entity_type, - "summaries": summary_fields, - "filters": filters} + params = {"type": entity_type, "summaries": summary_fields, "filters": filters} if include_archived_projects is False: # Defaults to True on the server, so only pass it if it's False @@ -1411,14 +1498,16 @@ def create(self, entity_type, data, return_fields=None): upload_filmstrip_image = None if "filmstrip_image" in data: if not self.server_caps.version or self.server_caps.version < (3, 1, 0): - raise ShotgunError("Filmstrip thumbnail support requires server version 3.1 or " - "higher, server is %s" % (self.server_caps.version,)) + raise ShotgunError( + "Filmstrip thumbnail support requires server version 3.1 or " + "higher, server is %s" % (self.server_caps.version,) + ) upload_filmstrip_image = data.pop("filmstrip_image") params = { "type": entity_type, "fields": self._dict_to_list(data), - "return_fields": return_fields + "return_fields": return_fields, } record = self._call_rpc("create", params, first=True) @@ -1426,12 +1515,20 @@ def create(self, entity_type, data, return_fields=None): if upload_image: self.upload_thumbnail(entity_type, result["id"], upload_image) - image = self.find_one(entity_type, [["id", "is", result.get("id")]], fields=["image"]) + image = self.find_one( + entity_type, [["id", "is", result.get("id")]], fields=["image"] + ) result["image"] = image.get("image") if upload_filmstrip_image: - self.upload_filmstrip_thumbnail(entity_type, result["id"], upload_filmstrip_image) - filmstrip = self.find_one(entity_type, [["id", "is", result.get("id")]], fields=["filmstrip_image"]) + self.upload_filmstrip_thumbnail( + entity_type, result["id"], upload_filmstrip_image + ) + filmstrip = self.find_one( + entity_type, + [["id", "is", result.get("id")]], + fields=["filmstrip_image"], + ) result["filmstrip_image"] = filmstrip.get("filmstrip_image") return result @@ -1480,12 +1577,16 @@ def update(self, entity_type, entity_id, data, multi_entity_update_modes=None): upload_filmstrip_image = None if "filmstrip_image" in data: if not self.server_caps.version or self.server_caps.version < (3, 1, 0): - raise ShotgunError("Filmstrip thumbnail support requires server version 3.1 or " - "higher, server is %s" % (self.server_caps.version,)) + raise ShotgunError( + "Filmstrip thumbnail support requires server version 3.1 or " + "higher, server is %s" % (self.server_caps.version,) + ) upload_filmstrip_image = data.pop("filmstrip_image") if data: - params = self._translate_update_params(entity_type, entity_id, data, multi_entity_update_modes) + params = self._translate_update_params( + entity_type, entity_id, data, multi_entity_update_modes + ) record = self._call_rpc("update", params) result = self._parse_records(record)[0] else: @@ -1493,12 +1594,20 @@ def update(self, entity_type, entity_id, data, multi_entity_update_modes=None): if upload_image: self.upload_thumbnail(entity_type, entity_id, upload_image) - image = self.find_one(entity_type, [["id", "is", result.get("id")]], fields=["image"]) + image = self.find_one( + entity_type, [["id", "is", result.get("id")]], fields=["image"] + ) result["image"] = image.get("image") if upload_filmstrip_image: - self.upload_filmstrip_thumbnail(entity_type, result["id"], upload_filmstrip_image) - filmstrip = self.find_one(entity_type, [["id", "is", result.get("id")]], fields=["filmstrip_image"]) + self.upload_filmstrip_thumbnail( + entity_type, result["id"], upload_filmstrip_image + ) + filmstrip = self.find_one( + entity_type, + [["id", "is", result.get("id")]], + fields=["filmstrip_image"], + ) result["filmstrip_image"] = filmstrip.get("filmstrip_image") return result @@ -1521,12 +1630,9 @@ def delete(self, entity_type, entity_id): entity was already deleted). :rtype: bool :raises: :class:`Fault` if entity does not exist (deleted or not). - """ + """ - params = { - "type": entity_type, - "id": entity_id - } + params = {"type": entity_type, "id": entity_id} return self._call_rpc("delete", params) @@ -1544,10 +1650,7 @@ def revive(self, entity_type, entity_id): :rtype: bool """ - params = { - "type": entity_type, - "id": entity_id - } + params = {"type": entity_type, "id": entity_id} return self._call_rpc("revive", params) @@ -1612,7 +1715,9 @@ def batch(self, requests): """ if not isinstance(requests, list): - raise ShotgunError("batch() expects a list. Instead was sent a %s" % type(requests)) + raise ShotgunError( + "batch() expects a list. Instead was sent a %s" % type(requests) + ) # If we have no requests, just return an empty list immediately. # Nothing to process means nothing to get results of. @@ -1624,39 +1729,42 @@ def batch(self, requests): def _required_keys(message, required_keys, data): missing = set(required_keys) - set(data.keys()) if missing: - raise ShotgunError("%s missing required key: %s. " - "Value was: %s." % (message, ", ".join(missing), data)) + raise ShotgunError( + "%s missing required key: %s. " + "Value was: %s." % (message, ", ".join(missing), data) + ) for req in requests: - _required_keys("Batched request", - ["request_type", "entity_type"], - req) - request_params = {"request_type": req["request_type"], "type": req["entity_type"]} + _required_keys("Batched request", ["request_type", "entity_type"], req) + request_params = { + "request_type": req["request_type"], + "type": req["entity_type"], + } if req["request_type"] == "create": _required_keys("Batched create request", ["data"], req) request_params["fields"] = self._dict_to_list(req["data"]) - request_params["return_fields"] = req.get("return_fields") or["id"] + request_params["return_fields"] = req.get("return_fields") or ["id"] elif req["request_type"] == "update": - _required_keys("Batched update request", - ["entity_id", "data"], - req) + _required_keys("Batched update request", ["entity_id", "data"], req) request_params["id"] = req["entity_id"] request_params["fields"] = self._dict_to_list( req["data"], extra_data=self._dict_to_extra_data( - req.get("multi_entity_update_modes"), - "multi_entity_update_mode" - ) + req.get("multi_entity_update_modes"), "multi_entity_update_mode" + ), ) if "multi_entity_update_mode" in req: - request_params["multi_entity_update_mode"] = req["multi_entity_update_mode"] + request_params["multi_entity_update_mode"] = req[ + "multi_entity_update_mode" + ] elif req["request_type"] == "delete": _required_keys("Batched delete request", ["entity_id"], req) request_params["id"] = req["entity_id"] else: - raise ShotgunError("Invalid request_type '%s' for batch" % ( - req["request_type"])) + raise ShotgunError( + "Invalid request_type '%s' for batch" % (req["request_type"]) + ) calls.append(request_params) records = self._call_rpc("batch", calls) return self._parse_records(records) @@ -1714,23 +1822,31 @@ def work_schedule_read(self, start_date, end_date, project=None, user=None): """ if not self.server_caps.version or self.server_caps.version < (3, 2, 0): - raise ShotgunError("Work schedule support requires server version 3.2 or " - "higher, server is %s" % (self.server_caps.version,)) + raise ShotgunError( + "Work schedule support requires server version 3.2 or " + "higher, server is %s" % (self.server_caps.version,) + ) if not isinstance(start_date, str) or not isinstance(end_date, str): - raise ShotgunError("The start_date and end_date arguments must be strings in YYYY-MM-DD format") + raise ShotgunError( + "The start_date and end_date arguments must be strings in YYYY-MM-DD format" + ) params = dict( - start_date=start_date, - end_date=end_date, - project=project, - user=user + start_date=start_date, end_date=end_date, project=project, user=user ) return self._call_rpc("work_schedule_read", params) - def work_schedule_update(self, date, working, description=None, project=None, user=None, - recalculate_field=None): + def work_schedule_update( + self, + date, + working, + description=None, + project=None, + user=None, + recalculate_field=None, + ): """ Update the work schedule for a given date. @@ -1765,8 +1881,10 @@ def work_schedule_update(self, date, working, description=None, project=None, us """ if not self.server_caps.version or self.server_caps.version < (3, 2, 0): - raise ShotgunError("Work schedule support requires server version 3.2 or " - "higher, server is %s" % (self.server_caps.version,)) + raise ShotgunError( + "Work schedule support requires server version 3.2 or " + "higher, server is %s" % (self.server_caps.version,) + ) if not isinstance(date, str): raise ShotgunError("The date argument must be string in YYYY-MM-DD format") @@ -1777,7 +1895,7 @@ def work_schedule_update(self, date, working, description=None, project=None, us description=description, project=project, user=user, - recalculate_field=recalculate_field + recalculate_field=recalculate_field, ) return self._call_rpc("work_schedule_update", params) @@ -1801,13 +1919,12 @@ def follow(self, user, entity): """ if not self.server_caps.version or self.server_caps.version < (5, 1, 22): - raise ShotgunError("Follow support requires server version 5.2 or " - "higher, server is %s" % (self.server_caps.version,)) + raise ShotgunError( + "Follow support requires server version 5.2 or " + "higher, server is %s" % (self.server_caps.version,) + ) - params = dict( - user=user, - entity=entity - ) + params = dict(user=user, entity=entity) return self._call_rpc("follow", params) @@ -1829,13 +1946,12 @@ def unfollow(self, user, entity): """ if not self.server_caps.version or self.server_caps.version < (5, 1, 22): - raise ShotgunError("Follow support requires server version 5.2 or " - "higher, server is %s" % (self.server_caps.version,)) + raise ShotgunError( + "Follow support requires server version 5.2 or " + "higher, server is %s" % (self.server_caps.version,) + ) - params = dict( - user=user, - entity=entity - ) + params = dict(user=user, entity=entity) return self._call_rpc("unfollow", params) @@ -1858,12 +1974,12 @@ def followers(self, entity): """ if not self.server_caps.version or self.server_caps.version < (5, 1, 22): - raise ShotgunError("Follow support requires server version 5.2 or " - "higher, server is %s" % (self.server_caps.version,)) + raise ShotgunError( + "Follow support requires server version 5.2 or " + "higher, server is %s" % (self.server_caps.version,) + ) - params = dict( - entity=entity - ) + params = dict(entity=entity) return self._call_rpc("followers", params) @@ -1890,9 +2006,7 @@ def following(self, user, project=None, entity_type=None): self.server_caps.ensure_user_following_support() - params = { - "user": user - } + params = {"user": user} if project: params["project"] = project if entity_type: @@ -2080,7 +2194,9 @@ def schema_field_read(self, entity_type, field_name=None, project_entity=None): return self._call_rpc("schema_field_read", params) - def schema_field_create(self, entity_type, data_type, display_name, properties=None): + def schema_field_create( + self, entity_type, data_type, display_name, properties=None + ): """ Create a field for the specified entity type. @@ -2109,15 +2225,17 @@ def schema_field_create(self, entity_type, data_type, display_name, properties=N params = { "type": entity_type, "data_type": data_type, - "properties": [ - {"property_name": "name", "value": display_name} - ] + "properties": [{"property_name": "name", "value": display_name}], } - params["properties"].extend(self._dict_to_list(properties, key_name="property_name", value_name="value")) + params["properties"].extend( + self._dict_to_list(properties, key_name="property_name", value_name="value") + ) return self._call_rpc("schema_field_create", params) - def schema_field_update(self, entity_type, field_name, properties, project_entity=None): + def schema_field_update( + self, entity_type, field_name, properties, project_entity=None + ): """ Update the properties for the specified field on an entity. @@ -2154,7 +2272,7 @@ def schema_field_update(self, entity_type, field_name, properties, project_entit "properties": [ {"property_name": k, "value": v} for k, v in six.iteritems((properties or {})) - ] + ], } params = self._add_project_param(params, project_entity) return self._call_rpc("schema_field_update", params) @@ -2172,10 +2290,7 @@ def schema_field_delete(self, entity_type, field_name): :rtype: bool """ - params = { - "type": entity_type, - "field_name": field_name - } + params = {"type": entity_type, "field_name": field_name} return self._call_rpc("schema_field_delete", params) @@ -2209,9 +2324,11 @@ def reset_user_agent(self): if self.config.no_ssl_validation: validation_str = "no-validate" - self._user_agents = ["shotgun-json (%s)" % __version__, - "Python %s (%s)" % (self.client_caps.py_version, ua_platform), - "ssl %s (%s)" % (self.client_caps.ssl_version, validation_str)] + self._user_agents = [ + "shotgun-json (%s)" % __version__, + "Python %s (%s)" % (self.client_caps.py_version, ua_platform), + "ssl %s (%s)" % (self.client_caps.ssl_version, validation_str), + ] def set_session_uuid(self, session_uuid): """ @@ -2229,8 +2346,14 @@ def set_session_uuid(self, session_uuid): self.config.session_uuid = session_uuid return - def share_thumbnail(self, entities, thumbnail_path=None, source_entity=None, - filmstrip_thumbnail=False, **kwargs): + def share_thumbnail( + self, + entities, + thumbnail_path=None, + source_entity=None, + filmstrip_thumbnail=False, + **kwargs, + ): """ Associate a thumbnail with more than one Shotgun entity. @@ -2246,7 +2369,7 @@ def share_thumbnail(self, entities, thumbnail_path=None, source_entity=None, .. note:: When sharing a filmstrip thumbnail, it is required to have a static thumbnail in place before the filmstrip will be displayed in the Shotgun web UI. - If the :ref:`thumbnail is still processing and is using a placeholder + If the :ref:`thumbnail is still processing and is using a placeholder `, this method will error. Simple use case: @@ -2273,48 +2396,58 @@ def share_thumbnail(self, entities, thumbnail_path=None, source_entity=None, share the static thumbnail. Defaults to ``False``. :returns: ``id`` of the Attachment entity representing the source thumbnail that is shared. :rtype: int - :raises: :class:`ShotgunError` if not supported by server version or improperly called, + :raises: :class:`ShotgunError` if not supported by server version or improperly called, or :class:`ShotgunThumbnailNotReady` if thumbnail is still pending. """ if not self.server_caps.version or self.server_caps.version < (4, 0, 0): - raise ShotgunError("Thumbnail sharing support requires server " - "version 4.0 or higher, server is %s" % (self.server_caps.version,)) + raise ShotgunError( + "Thumbnail sharing support requires server " + "version 4.0 or higher, server is %s" % (self.server_caps.version,) + ) if not isinstance(entities, list) or len(entities) == 0: - raise ShotgunError("'entities' parameter must be a list of entity " - "hashes and may not be empty") + raise ShotgunError( + "'entities' parameter must be a list of entity " + "hashes and may not be empty" + ) for e in entities: if not isinstance(e, dict) or "id" not in e or "type" not in e: - raise ShotgunError("'entities' parameter must be a list of " - "entity hashes with at least 'type' and 'id' keys.\nInvalid " - "entity: %s" % e) + raise ShotgunError( + "'entities' parameter must be a list of " + "entity hashes with at least 'type' and 'id' keys.\nInvalid " + "entity: %s" % e + ) - if (not thumbnail_path and not source_entity) or (thumbnail_path and source_entity): - raise ShotgunError("You must supply either thumbnail_path OR source_entity.") + if (not thumbnail_path and not source_entity) or ( + thumbnail_path and source_entity + ): + raise ShotgunError( + "You must supply either thumbnail_path OR source_entity." + ) # upload thumbnail if thumbnail_path: source_entity = entities.pop(0) if filmstrip_thumbnail: thumb_id = self.upload_filmstrip_thumbnail( - source_entity["type"], - source_entity["id"], - thumbnail_path, - **kwargs + source_entity["type"], source_entity["id"], thumbnail_path, **kwargs ) else: thumb_id = self.upload_thumbnail( - source_entity["type"], - source_entity["id"], - thumbnail_path, - **kwargs + source_entity["type"], source_entity["id"], thumbnail_path, **kwargs ) else: - if not isinstance(source_entity, dict) or "id" not in source_entity or "type" not in source_entity: - raise ShotgunError("'source_entity' parameter must be a dict " - "with at least 'type' and 'id' keys.\nGot: %s (%s)" - % (source_entity, type(source_entity))) + if ( + not isinstance(source_entity, dict) + or "id" not in source_entity + or "type" not in source_entity + ): + raise ShotgunError( + "'source_entity' parameter must be a dict " + "with at least 'type' and 'id' keys.\nGot: %s (%s)" + % (source_entity, type(source_entity)) + ) # only 1 entity in list and we already uploaded the thumbnail to it if len(entities) == 0: @@ -2333,8 +2466,16 @@ def share_thumbnail(self, entities, thumbnail_path=None, source_entity=None, "filmstrip_thumbnail": filmstrip_thumbnail, } - url = urllib.parse.urlunparse((self.config.scheme, self.config.server, - "/upload/share_thumbnail", None, None, None)) + url = urllib.parse.urlunparse( + ( + self.config.scheme, + self.config.server, + "/upload/share_thumbnail", + None, + None, + None, + ) + ) result = self._send_form(url, params) @@ -2377,7 +2518,9 @@ def upload_thumbnail(self, entity_type, entity_id, path, **kwargs): :param str path: Full path to the thumbnail file on disk. :returns: Id of the new attachment """ - return self.upload(entity_type, entity_id, path, field_name="thumb_image", **kwargs) + return self.upload( + entity_type, entity_id, path, field_name="thumb_image", **kwargs + ) def upload_filmstrip_thumbnail(self, entity_type, entity_id, path, **kwargs): """ @@ -2419,13 +2562,24 @@ def upload_filmstrip_thumbnail(self, entity_type, entity_id, path, **kwargs): :rtype: int """ if not self.server_caps.version or self.server_caps.version < (3, 1, 0): - raise ShotgunError("Filmstrip thumbnail support requires server version 3.1 or " - "higher, server is %s" % (self.server_caps.version,)) + raise ShotgunError( + "Filmstrip thumbnail support requires server version 3.1 or " + "higher, server is %s" % (self.server_caps.version,) + ) - return self.upload(entity_type, entity_id, path, field_name="filmstrip_thumb_image", **kwargs) + return self.upload( + entity_type, entity_id, path, field_name="filmstrip_thumb_image", **kwargs + ) - def upload(self, entity_type, entity_id, path, field_name=None, display_name=None, - tag_list=None): + def upload( + self, + entity_type, + entity_id, + path, + field_name=None, + display_name=None, + tag_list=None, + ): """ Upload a file to the specified entity. @@ -2434,7 +2588,7 @@ def upload(self, entity_type, entity_id, path, field_name=None, display_name=Non assign tags to the Attachment. .. note:: - Make sure to have retries for file uploads. Failures when uploading will occasionally happen. + Make sure to have retries for file uploads. Failures when uploading will occasionally happen. When it does, immediately retrying to upload usually works >>> mov_file = '/data/show/ne2/100_110/anim/01.mlk-02b.mov' @@ -2477,19 +2631,45 @@ def upload(self, entity_type, entity_id, path, field_name=None, display_name=Non if os.path.getsize(path) == 0: raise ShotgunError("Path cannot be an empty file: '%s'" % path) - is_thumbnail = (field_name in ["thumb_image", "filmstrip_thumb_image", "image", - "filmstrip_image"]) + is_thumbnail = field_name in [ + "thumb_image", + "filmstrip_thumb_image", + "image", + "filmstrip_image", + ] # Supported types can be directly uploaded to Cloud storage if self._requires_direct_s3_upload(entity_type, field_name): - return self._upload_to_storage(entity_type, entity_id, path, field_name, display_name, - tag_list, is_thumbnail) + return self._upload_to_storage( + entity_type, + entity_id, + path, + field_name, + display_name, + tag_list, + is_thumbnail, + ) else: - return self._upload_to_sg(entity_type, entity_id, path, field_name, display_name, - tag_list, is_thumbnail) + return self._upload_to_sg( + entity_type, + entity_id, + path, + field_name, + display_name, + tag_list, + is_thumbnail, + ) - def _upload_to_storage(self, entity_type, entity_id, path, field_name, display_name, - tag_list, is_thumbnail): + def _upload_to_storage( + self, + entity_type, + entity_id, + path, + field_name, + display_name, + tag_list, + is_thumbnail, + ): """ Internal function to upload a file to the Cloud storage and link it to the specified entity. @@ -2509,9 +2689,11 @@ def _upload_to_storage(self, entity_type, entity_id, path, field_name, display_n # Step 1: get the upload url - is_multipart_upload = (os.path.getsize(path) > self._MULTIPART_UPLOAD_CHUNK_SIZE) + is_multipart_upload = os.path.getsize(path) > self._MULTIPART_UPLOAD_CHUNK_SIZE - upload_info = self._get_attachment_upload_info(is_thumbnail, filename, is_multipart_upload) + upload_info = self._get_attachment_upload_info( + is_thumbnail, filename, is_multipart_upload + ) # Step 2: upload the file # We upload large files in multiple parts because it is more robust @@ -2523,13 +2705,21 @@ def _upload_to_storage(self, entity_type, entity_id, path, field_name, display_n # Step 3: create the attachment - url = urllib.parse.urlunparse((self.config.scheme, self.config.server, - "/upload/api_link_file", None, None, None)) + url = urllib.parse.urlunparse( + ( + self.config.scheme, + self.config.server, + "/upload/api_link_file", + None, + None, + None, + ) + ) params = { "entity_type": entity_type, "entity_id": entity_id, - "upload_link_info": upload_info["upload_info"] + "upload_link_info": upload_info["upload_info"], } params.update(self._auth_params()) @@ -2550,17 +2740,26 @@ def _upload_to_storage(self, entity_type, entity_id, path, field_name, display_n result = self._send_form(url, params) if not result.startswith("1"): - raise ShotgunError("Could not upload file successfully, but " - "not sure why.\nPath: %s\nUrl: %s\nError: %s" - % (path, url, result)) + raise ShotgunError( + "Could not upload file successfully, but " + "not sure why.\nPath: %s\nUrl: %s\nError: %s" % (path, url, result) + ) LOG.debug("Attachment linked to content on Cloud storage") attachment_id = int(result.split(":", 2)[1].split("\n", 1)[0]) return attachment_id - def _upload_to_sg(self, entity_type, entity_id, path, field_name, display_name, - tag_list, is_thumbnail): + def _upload_to_sg( + self, + entity_type, + entity_id, + path, + field_name, + display_name, + tag_list, + is_thumbnail, + ): """ Internal function to upload a file to Shotgun and link it to the specified entity. @@ -2585,14 +2784,30 @@ def _upload_to_sg(self, entity_type, entity_id, path, field_name, display_name, params.update(self._auth_params()) if is_thumbnail: - url = urllib.parse.urlunparse((self.config.scheme, self.config.server, - "/upload/publish_thumbnail", None, None, None)) + url = urllib.parse.urlunparse( + ( + self.config.scheme, + self.config.server, + "/upload/publish_thumbnail", + None, + None, + None, + ) + ) params["thumb_image"] = open(path, "rb") if field_name == "filmstrip_thumb_image" or field_name == "filmstrip_image": params["filmstrip"] = True else: - url = urllib.parse.urlunparse((self.config.scheme, self.config.server, - "/upload/upload_file", None, None, None)) + url = urllib.parse.urlunparse( + ( + self.config.scheme, + self.config.server, + "/upload/upload_file", + None, + None, + None, + ) + ) if display_name is None: display_name = os.path.basename(path) # we allow linking to nothing for generic reference use cases @@ -2608,9 +2823,10 @@ def _upload_to_sg(self, entity_type, entity_id, path, field_name, display_name, result = self._send_form(url, params) if not result.startswith("1"): - raise ShotgunError("Could not upload file successfully, but " - "not sure why.\nPath: %s\nUrl: %s\nError: %s" - % (path, url, result)) + raise ShotgunError( + "Could not upload file successfully, but " + "not sure why.\nPath: %s\nUrl: %s\nError: %s" % (path, url, result) + ) attachment_id = int(result.split(":", 2)[1].split("\n", 1)[0]) return attachment_id @@ -2633,21 +2849,22 @@ def _get_attachment_upload_info(self, is_thumbnail, filename, is_multipart_uploa else: upload_type = "Attachment" - params = { - "upload_type": upload_type, - "filename": filename - } + params = {"upload_type": upload_type, "filename": filename} params["multipart_upload"] = is_multipart_upload upload_url = "/upload/api_get_upload_link_info" - url = urllib.parse.urlunparse((self.config.scheme, self.config.server, upload_url, None, None, None)) + url = urllib.parse.urlunparse( + (self.config.scheme, self.config.server, upload_url, None, None, None) + ) upload_info = self._send_form(url, params) if not upload_info.startswith("1"): - raise ShotgunError("Could not get upload_url but " - "not sure why.\nPath: %s\nUrl: %s\nError: %s" - % (filename, url, upload_info)) + raise ShotgunError( + "Could not get upload_url but " + "not sure why.\nPath: %s\nUrl: %s\nError: %s" + % (filename, url, upload_info) + ) LOG.debug("Completed rpc call to %s" % (upload_url)) @@ -2658,7 +2875,7 @@ def _get_attachment_upload_info(self, is_thumbnail, filename, is_multipart_uploa "timestamp": upload_info_parts[2], "upload_type": upload_info_parts[3], "upload_id": upload_info_parts[4], - "upload_info": upload_info + "upload_info": upload_info, } def download_attachment(self, attachment=False, file_path=None, attachment_id=None): @@ -2702,16 +2919,19 @@ def download_attachment(self, attachment=False, file_path=None, attachment_id=No if type(attachment_id) == int: attachment = attachment_id else: - raise TypeError("Missing parameter 'attachment'. Expected a " - "dict, int, NoneType value or" - "an int for parameter attachment_id") + raise TypeError( + "Missing parameter 'attachment'. Expected a " + "dict, int, NoneType value or" + "an int for parameter attachment_id" + ) # write to disk if file_path: try: fp = open(file_path, "wb") except IOError as e: - raise IOError("Unable to write Attachment to disk using " - "file_path. %s" % e) + raise IOError( + "Unable to write Attachment to disk using " "file_path. %s" % e + ) url = self.get_attachment_download_url(attachment) if url is None: @@ -2742,7 +2962,10 @@ def download_attachment(self, attachment=False, file_path=None, attachment_id=No err += "\nAttachment may not exist or is a local file?" elif e.code == 403: # Only parse the body if it is an Amazon S3 url. - if url.find("s3.amazonaws.com") != -1 and e.headers["content-type"] == "application/xml": + if ( + url.find("s3.amazonaws.com") != -1 + and e.headers["content-type"] == "application/xml" + ): body = [sgutils.ensure_text(line) for line in e.readlines()] if body: xml = "".join(body) @@ -2778,8 +3001,24 @@ def get_auth_cookie_handler(self): """ sid = self.get_session_token() cj = http_cookiejar.LWPCookieJar() - c = http_cookiejar.Cookie("0", "_session_id", sid, None, False, self.config.server, False, - False, "/", True, False, None, True, None, None, {}) + c = http_cookiejar.Cookie( + "0", + "_session_id", + sid, + None, + False, + self.config.server, + False, + False, + "/", + True, + False, + None, + True, + None, + None, + {}, + ) cj.set_cookie(c) return urllib.request.HTTPCookieProcessor(cj) @@ -2811,20 +3050,34 @@ def get_attachment_download_url(self, attachment): try: url = attachment["url"] except KeyError: - if ("id" in attachment and "type" in attachment and attachment["type"] == "Attachment"): + if ( + "id" in attachment + and "type" in attachment + and attachment["type"] == "Attachment" + ): attachment_id = attachment["id"] else: raise ValueError("Missing 'url' key in Attachment dict") elif attachment is None: url = None else: - raise TypeError("Unable to determine download url. Expected " - "dict, int, or NoneType. Instead got %s" % type(attachment)) + raise TypeError( + "Unable to determine download url. Expected " + "dict, int, or NoneType. Instead got %s" % type(attachment) + ) if attachment_id: - url = urllib.parse.urlunparse((self.config.scheme, self.config.server, - "/file_serve/attachment/%s" % urllib.parse.quote(str(attachment_id)), - None, None, None)) + url = urllib.parse.urlunparse( + ( + self.config.scheme, + self.config.server, + "/file_serve/attachment/%s" + % urllib.parse.quote(str(attachment_id)), + None, + None, + None, + ) + ) return url def authenticate_human_user(self, user_login, user_password, auth_token=None): @@ -2862,9 +3115,13 @@ def authenticate_human_user(self, user_login, user_password, auth_token=None): self.config.auth_token = auth_token try: - data = self.find_one("HumanUser", [["sg_status_list", "is", "act"], - ["login", "is", user_login]], - ["id", "login"], "", "all") + data = self.find_one( + "HumanUser", + [["sg_status_list", "is", "act"], ["login", "is", user_login]], + ["id", "login"], + "", + "all", + ) # Set back to default - There finally and except cannot be used together in python2.4 self.config.user_login = original_login self.config.user_password = original_password @@ -2902,18 +3159,26 @@ def update_project_last_accessed(self, project, user=None): value from the current instance will be used instead. """ if self.server_caps.version and self.server_caps.version < (5, 3, 20): - raise ShotgunError("update_project_last_accessed requires server version 5.3.20 or " - "higher, server is %s" % (self.server_caps.version,)) + raise ShotgunError( + "update_project_last_accessed requires server version 5.3.20 or " + "higher, server is %s" % (self.server_caps.version,) + ) if not user: # Try to use sudo as user if present if self.config.sudo_as_login: - user = self.find_one("HumanUser", [["login", "is", self.config.sudo_as_login]]) + user = self.find_one( + "HumanUser", [["login", "is", self.config.sudo_as_login]] + ) # Try to use login if present if self.config.user_login: - user = self.find_one("HumanUser", [["login", "is", self.config.user_login]]) + user = self.find_one( + "HumanUser", [["login", "is", self.config.user_login]] + ) - params = {"project_id": project["id"], } + params = { + "project_id": project["id"], + } if user: params["user_id"] = user["id"] @@ -2979,8 +3244,10 @@ def note_thread_read(self, note_id, entity_fields=None): """ if self.server_caps.version and self.server_caps.version < (6, 2, 0): - raise ShotgunError("note_thread requires server version 6.2.0 or " - "higher, server is %s" % (self.server_caps.version,)) + raise ShotgunError( + "note_thread requires server version 6.2.0 or " + "higher, server is %s" % (self.server_caps.version,) + ) entity_fields = entity_fields or {} @@ -3050,8 +3317,10 @@ def text_search(self, text, entity_types, project_ids=None, limit=None): :rtype: dict """ if self.server_caps.version and self.server_caps.version < (6, 2, 0): - raise ShotgunError("auto_complete requires server version 6.2.0 or " - "higher, server is %s" % (self.server_caps.version,)) + raise ShotgunError( + "auto_complete requires server version 6.2.0 or " + "higher, server is %s" % (self.server_caps.version,) + ) # convert entity_types structure into the form # that the API endpoint expects @@ -3059,28 +3328,39 @@ def text_search(self, text, entity_types, project_ids=None, limit=None): raise ValueError("entity_types parameter must be a dictionary") api_entity_types = {} - for (entity_type, filter_list) in six.iteritems(entity_types): + for entity_type, filter_list in six.iteritems(entity_types): if isinstance(filter_list, (list, tuple)): resolved_filters = _translate_filters(filter_list, filter_operator=None) api_entity_types[entity_type] = resolved_filters else: - raise ValueError("value of entity_types['%s'] must " - "be a list or tuple." % entity_type) + raise ValueError( + "value of entity_types['%s'] must " + "be a list or tuple." % entity_type + ) project_ids = project_ids or [] - params = {"text": text, - "entity_types": api_entity_types, - "project_ids": project_ids, - "max_results": limit} + params = { + "text": text, + "entity_types": api_entity_types, + "project_ids": project_ids, + "max_results": limit, + } record = self._call_rpc("query_display_name_cache", params) result = self._parse_records(record)[0] return result - def activity_stream_read(self, entity_type, entity_id, entity_fields=None, min_id=None, - max_id=None, limit=None): + def activity_stream_read( + self, + entity_type, + entity_id, + entity_fields=None, + min_id=None, + max_id=None, + limit=None, + ): """ Retrieve activity stream data from Shotgun. @@ -3146,8 +3426,10 @@ def activity_stream_read(self, entity_type, entity_id, entity_fields=None, min_i :rtype: dict """ if self.server_caps.version and self.server_caps.version < (6, 2, 0): - raise ShotgunError("activity_stream requires server version 6.2.0 or " - "higher, server is %s" % (self.server_caps.version,)) + raise ShotgunError( + "activity_stream requires server version 6.2.0 or " + "higher, server is %s" % (self.server_caps.version,) + ) # set up parameters to send to server. entity_fields = entity_fields or {} @@ -3155,12 +3437,14 @@ def activity_stream_read(self, entity_type, entity_id, entity_fields=None, min_i if not isinstance(entity_fields, dict): raise ValueError("entity_fields parameter must be a dictionary") - params = {"type": entity_type, - "id": entity_id, - "max_id": max_id, - "min_id": min_id, - "limit": limit, - "entity_fields": entity_fields} + params = { + "type": entity_type, + "id": entity_id, + "max_id": max_id, + "min_id": min_id, + "limit": limit, + "entity_fields": entity_fields, + } record = self._call_rpc("activity_stream", params) result = self._parse_records(record)[0] @@ -3182,8 +3466,8 @@ def nav_expand(self, path, seed_entity_field=None, entity_fields=None): { "path": path, "seed_entity_field": seed_entity_field, - "entity_fields": entity_fields - } + "entity_fields": entity_fields, + }, ) def nav_search_string(self, root_path, search_string, seed_entity_field=None): @@ -3201,8 +3485,8 @@ def nav_search_string(self, root_path, search_string, seed_entity_field=None): { "root_path": root_path, "seed_entity_field": seed_entity_field, - "search_criteria": {"search_string": search_string} - } + "search_criteria": {"search_string": search_string}, + }, ) def nav_search_entity(self, root_path, entity, seed_entity_field=None): @@ -3221,8 +3505,8 @@ def nav_search_entity(self, root_path, entity, seed_entity_field=None): { "root_path": root_path, "seed_entity_field": seed_entity_field, - "search_criteria": {"entity": entity} - } + "search_criteria": {"entity": entity}, + }, ) def get_session_token(self): @@ -3263,8 +3547,10 @@ def preferences_read(self, prefs=None): :rtype: dict """ if self.server_caps.version and self.server_caps.version < (7, 10, 0): - raise ShotgunError("preferences_read requires server version 7.10.0 or " - "higher, server is %s" % (self.server_caps.version,)) + raise ShotgunError( + "preferences_read requires server version 7.10.0 or " + "higher, server is %s" % (self.server_caps.version,) + ) prefs = prefs or [] @@ -3297,10 +3583,7 @@ def user_subscriptions_create(self, users): :rtype: bool """ - response = self._call_rpc( - "user_subscriptions_create", - {"users": users} - ) + response = self._call_rpc("user_subscriptions_create", {"users": users}) if not isinstance(response, dict): return False @@ -3390,9 +3673,14 @@ def _turn_off_ssl_validation(self): self.config.no_ssl_validation = True NO_SSL_VALIDATION = True # reset ssl-validation in user-agents - self._user_agents = ["ssl %s (no-validate)" % self.client_caps.ssl_version - if ua.startswith("ssl ") else ua - for ua in self._user_agents] + self._user_agents = [ + ( + "ssl %s (no-validate)" % self.client_caps.ssl_version + if ua.startswith("ssl ") + else ua + ) + for ua in self._user_agents + ] # Deprecated methods from old wrapper def schema(self, entity_type): @@ -3400,7 +3688,9 @@ def schema(self, entity_type): .. deprecated:: 3.0.0 Use :meth:`~shotgun_api3.Shotgun.schema_field_read` instead. """ - raise ShotgunError("Deprecated: use schema_field_read('%s') instead" % entity_type) + raise ShotgunError( + "Deprecated: use schema_field_read('%s') instead" % entity_type + ) def entity_types(self): """ @@ -3408,6 +3698,7 @@ def entity_types(self): Use :meth:`~shotgun_api3.Shotgun.schema_entity_read` instead. """ raise ShotgunError("Deprecated: use schema_entity_read() instead") + # ======================================================================== # RPC Functions @@ -3416,16 +3707,17 @@ def _call_rpc(self, method, params, include_auth_params=True, first=False): Call the specified method on the Shotgun Server sending the supplied payload. """ - LOG.debug("Starting rpc call to %s with params %s" % ( - method, params)) + LOG.debug("Starting rpc call to %s with params %s" % (method, params)) params = self._transform_outbound(params) - payload = self._build_payload(method, params, include_auth_params=include_auth_params) + payload = self._build_payload( + method, params, include_auth_params=include_auth_params + ) encoded_payload = self._encode_payload(payload) req_headers = { "content-type": "application/json; charset=utf-8", - "connection": "keep-alive" + "connection": "keep-alive", } if self.config.localized is True: @@ -3497,8 +3789,10 @@ def _auth_params(self): # Authenticate using session_id elif self.config.session_token: if self.server_caps.version and self.server_caps.version < (5, 3, 0): - raise ShotgunError("Session token based authentication requires server version " - "5.3.0 or higher, server is %s" % (self.server_caps.version,)) + raise ShotgunError( + "Session token based authentication requires server version " + "5.3.0 or higher, server is %s" % (self.server_caps.version,) + ) auth_params = {"session_token": str(self.config.session_token)} @@ -3516,8 +3810,10 @@ def _auth_params(self): # Make sure sudo_as_login is supported by server version if self.config.sudo_as_login: if self.server_caps.version and self.server_caps.version < (5, 3, 12): - raise ShotgunError("Option 'sudo_as_login' requires server version 5.3.12 or " - "higher, server is %s" % (self.server_caps.version,)) + raise ShotgunError( + "Option 'sudo_as_login' requires server version 5.3.12 or " + "higher, server is %s" % (self.server_caps.version,) + ) auth_params["sudo_as_login"] = self.config.sudo_as_login if self.config.extra_auth_params: @@ -3552,10 +3848,7 @@ def _build_payload(self, method, params, include_auth_params=True): if params: call_params.append(params) - return { - "method_name": method, - "params": call_params - } + return {"method_name": method, "params": call_params} def _encode_payload(self, payload): """ @@ -3588,7 +3881,7 @@ def _make_call(self, verb, path, body, headers): max_rpc_attempts = self.config.max_rpc_attempts rpc_attempt_interval = self.config.rpc_attempt_interval / 1000.0 - while (attempt < max_rpc_attempts): + while attempt < max_rpc_attempts: attempt += 1 try: return self._http_request(verb, path, body, req_headers) @@ -3624,15 +3917,19 @@ def _make_call(self, verb, path, body, headers): # unknown message digest algorithm # # Any other exceptions simply get raised. - if "unknown message digest algorithm" not in str(e) or \ - "SHOTGUN_FORCE_CERTIFICATE_VALIDATION" in os.environ: + if ( + "unknown message digest algorithm" not in str(e) + or "SHOTGUN_FORCE_CERTIFICATE_VALIDATION" in os.environ + ): raise if self.config.no_ssl_validation is False: - LOG.warning("SSL Error: this Python installation is incompatible with " - "certificates signed with SHA-2. Disabling certificate validation. " - "For more information, see https://www.shotgridsoftware.com/blog/" - "important-ssl-certificate-renewal-and-sha-2/") + LOG.warning( + "SSL Error: this Python installation is incompatible with " + "certificates signed with SHA-2. Disabling certificate validation. " + "For more information, see https://www.shotgridsoftware.com/blog/" + "important-ssl-certificate-renewal-and-sha-2/" + ) self._turn_off_ssl_validation() # reload user agent to reflect that we have turned off ssl validation req_headers["user-agent"] = "; ".join(self._user_agents) @@ -3648,8 +3945,8 @@ def _make_call(self, verb, path, body, headers): raise LOG.debug( - "Request failed, attempt %d of %d. Retrying in %.2f seconds..." % - (attempt, max_rpc_attempts, rpc_attempt_interval) + "Request failed, attempt %d of %d. Retrying in %.2f seconds..." + % (attempt, max_rpc_attempts, rpc_attempt_interval) ) time.sleep(rpc_attempt_interval) @@ -3657,7 +3954,9 @@ def _http_request(self, verb, path, body, headers): """ Make the actual HTTP request. """ - url = urllib.parse.urlunparse((self.config.scheme, self.config.server, path, None, None, None)) + url = urllib.parse.urlunparse( + (self.config.scheme, self.config.server, path, None, None, None) + ) LOG.debug("Request is %s:%s" % (verb, url)) LOG.debug("Request headers are %s" % headers) LOG.debug("Request body is %s" % body) @@ -3666,10 +3965,7 @@ def _http_request(self, verb, path, body, headers): resp, content = conn.request(url, method=verb, body=body, headers=headers) # http response code is handled else where http_status = (resp.status, resp.reason) - resp_headers = dict( - (k.lower(), v) - for k, v in six.iteritems(resp) - ) + resp_headers = dict((k.lower(), v) for k, v in six.iteritems(resp)) resp_body = content LOG.debug("Response status is %s %s" % http_status) @@ -3704,10 +4000,7 @@ def _parse_http_status(self, status): headers = "HTTP error from server" if status[0] == 503: errmsg = "Flow Production Tracking is currently down for maintenance or too busy to reply. Please try again later." - raise ProtocolError(self.config.server, - error_code, - errmsg, - headers) + raise ProtocolError(self.config.server, error_code, errmsg, headers) return @@ -3739,6 +4032,7 @@ def _json_loads_ascii(self, body): """ See http://stackoverflow.com/questions/956867 """ + def _decode_list(lst): newlist = [] for i in lst: @@ -3760,6 +4054,7 @@ def _decode_dict(dct): v = _decode_list(v) newdict[k] = v return newdict + return json.loads(body, object_hook=_decode_dict) def _response_errors(self, sg_response): @@ -3780,21 +4075,28 @@ def _response_errors(self, sg_response): if isinstance(sg_response, dict) and sg_response.get("exception"): if sg_response.get("error_code") == ERR_AUTH: - raise AuthenticationFault(sg_response.get("message", "Unknown Authentication Error")) + raise AuthenticationFault( + sg_response.get("message", "Unknown Authentication Error") + ) elif sg_response.get("error_code") == ERR_2FA: raise MissingTwoFactorAuthenticationFault( sg_response.get("message", "Unknown 2FA Authentication Error") ) elif sg_response.get("error_code") == ERR_SSO: raise UserCredentialsNotAllowedForSSOAuthenticationFault( - sg_response.get("message", - "Authentication using username/password is not " - "allowed for an SSO-enabled Flow Production Tracking site") + sg_response.get( + "message", + "Authentication using username/password is not " + "allowed for an SSO-enabled Flow Production Tracking site", + ) ) elif sg_response.get("error_code") == ERR_OXYG: raise UserCredentialsNotAllowedForOxygenAuthenticationFault( - sg_response.get("message", "Authentication using username/password is not " - "allowed for an Autodesk Identity enabled Flow Production Tracking site") + sg_response.get( + "message", + "Authentication using username/password is not " + "allowed for an Autodesk Identity enabled Flow Production Tracking site", + ) ) else: # raise general Fault @@ -3817,10 +4119,7 @@ def _visit_data(self, data, visitor): return tuple(recursive(i, visitor) for i in data) if isinstance(data, dict): - return dict( - (k, recursive(v, visitor)) - for k, v in six.iteritems(data) - ) + return dict((k, recursive(v, visitor)) for k, v in six.iteritems(data)) return visitor(data) @@ -3833,10 +4132,12 @@ def _transform_outbound(self, data): """ if self.config.convert_datetimes_to_utc: + def _change_tz(value): if value.tzinfo is None: value = value.replace(tzinfo=SG_TIMEZONE.local) return value.astimezone(SG_TIMEZONE.utc) + else: _change_tz = None @@ -3859,7 +4160,7 @@ def _outbound_visitor(value): hour=value.hour, minute=value.minute, second=value.second, - microsecond=value.microsecond + microsecond=value.microsecond, ) if _change_tz: value = _change_tz(value) @@ -3881,8 +4182,10 @@ def _transform_inbound(self, data): # to the local time, otherwise it will fail to compare to datetimes # that do not have a time zone. if self.config.convert_datetimes_to_utc: + def _change_tz(x): return x.replace(tzinfo=SG_TIMEZONE.utc).astimezone(SG_TIMEZONE.local) + else: _change_tz = None @@ -3892,7 +4195,8 @@ def _inbound_visitor(value): try: # strptime was not on datetime in python2.4 value = datetime.datetime( - *time.strptime(value, "%Y-%m-%dT%H:%M:%SZ")[:6]) + *time.strptime(value, "%Y-%m-%dT%H:%M:%SZ")[:6] + ) except ValueError: return value if _change_tz: @@ -3914,14 +4218,26 @@ def _get_connection(self): return self._connection if self.config.proxy_server: - pi = ProxyInfo(socks.PROXY_TYPE_HTTP, self.config.proxy_server, - self.config.proxy_port, proxy_user=self.config.proxy_user, - proxy_pass=self.config.proxy_pass) - self._connection = Http(timeout=self.config.timeout_secs, ca_certs=self.__ca_certs, - proxy_info=pi, disable_ssl_certificate_validation=self.config.no_ssl_validation) + pi = ProxyInfo( + socks.PROXY_TYPE_HTTP, + self.config.proxy_server, + self.config.proxy_port, + proxy_user=self.config.proxy_user, + proxy_pass=self.config.proxy_pass, + ) + self._connection = Http( + timeout=self.config.timeout_secs, + ca_certs=self.__ca_certs, + proxy_info=pi, + disable_ssl_certificate_validation=self.config.no_ssl_validation, + ) else: - self._connection = Http(timeout=self.config.timeout_secs, ca_certs=self.__ca_certs, - proxy_info=None, disable_ssl_certificate_validation=self.config.no_ssl_validation) + self._connection = Http( + timeout=self.config.timeout_secs, + ca_certs=self.__ca_certs, + proxy_info=None, + disable_ssl_certificate_validation=self.config.no_ssl_validation, + ) return self._connection @@ -3940,6 +4256,7 @@ def _close_connection(self): self._connection.connections.clear() self._connection = None return + # ======================================================================== # Utility @@ -3961,7 +4278,9 @@ def _parse_records(self, records): return [] if not isinstance(records, (list, tuple)): - records = [records, ] + records = [ + records, + ] for rec in records: # skip results that aren't entity dictionaries @@ -3978,11 +4297,19 @@ def _parse_records(self, records): rec[k] = rec[k].replace("<", "<") # check for thumbnail for older version (<3.3.0) of shotgun - if k == "image" and self.server_caps.version and self.server_caps.version < (3, 3, 0): + if ( + k == "image" + and self.server_caps.version + and self.server_caps.version < (3, 3, 0) + ): rec["image"] = self._build_thumb_url(rec["type"], rec["id"]) continue - if isinstance(v, dict) and v.get("link_type") == "local" and self.client_caps.local_path_field in v: + if ( + isinstance(v, dict) + and v.get("link_type") == "local" + and self.client_caps.local_path_field in v + ): local_path = v[self.client_caps.local_path_field] v["local_path"] = local_path v["url"] = "file://%s" % (local_path or "",) @@ -4003,10 +4330,14 @@ def _build_thumb_url(self, entity_type, entity_id): # curl "https://foo.com/upload/get_thumbnail_url?entity_type=Version&entity_id=1" # 1 # /files/0000/0000/0012/232/shot_thumb.jpg.jpg - entity_info = {"e_type": urllib.parse.quote(entity_type), - "e_id": urllib.parse.quote(str(entity_id))} - url = ("/upload/get_thumbnail_url?" + - "entity_type=%(e_type)s&entity_id=%(e_id)s" % entity_info) + entity_info = { + "e_type": urllib.parse.quote(entity_type), + "e_id": urllib.parse.quote(str(entity_id)), + } + url = ( + "/upload/get_thumbnail_url?" + + "entity_type=%(e_type)s&entity_id=%(e_id)s" % entity_info + ) body = self._make_call("GET", url, None, None)[2] @@ -4018,15 +4349,23 @@ def _build_thumb_url(self, entity_type, entity_id): raise ShotgunError(thumb_url) if code == 1: - return urllib.parse.urlunparse((self.config.scheme, - self.config.server, - thumb_url.strip(), - None, None, None)) + return urllib.parse.urlunparse( + ( + self.config.scheme, + self.config.server, + thumb_url.strip(), + None, + None, + None, + ) + ) # Comments in prev version said we can get this sometimes. raise RuntimeError("Unknown code %s %s" % (code, thumb_url)) - def _dict_to_list(self, d, key_name="field_name", value_name="value", extra_data=None): + def _dict_to_list( + self, d, key_name="field_name", value_name="value", extra_data=None + ): """ Utility function to convert a dict into a list dicts using the key_name and value_name keys. @@ -4098,8 +4437,14 @@ def _multipart_upload_file_to_storage(self, path, upload_info): # encoded. data = BytesIO(data) bytes_read += data_size - part_url = self._get_upload_part_link(upload_info, filename, part_number) - etags.append(self._upload_data_to_storage(data, content_type, data_size, part_url)) + part_url = self._get_upload_part_link( + upload_info, filename, part_number + ) + etags.append( + self._upload_data_to_storage( + data, content_type, data_size, part_url + ) + ) part_number += 1 self._complete_multipart_upload(upload_info, filename, etags) @@ -4124,11 +4469,19 @@ def _get_upload_part_link(self, upload_info, filename, part_number): "filename": filename, "timestamp": upload_info["timestamp"], "upload_id": upload_info["upload_id"], - "part_number": part_number + "part_number": part_number, } - url = urllib.parse.urlunparse((self.config.scheme, self.config.server, - "/upload/api_get_upload_link_for_part", None, None, None)) + url = urllib.parse.urlunparse( + ( + self.config.scheme, + self.config.server, + "/upload/api_get_upload_link_for_part", + None, + None, + None, + ) + ) result = self._send_form(url, params) # Response is of the form: 1\n (for success) or 0\n (for failure). @@ -4172,9 +4525,15 @@ def _upload_data_to_storage(self, data, content_type, size, storage_url): attempt += 1 continue elif e.code in [500, 503]: - raise ShotgunError("Got a %s response when uploading to %s: %s" % (e.code, storage_url, e)) + raise ShotgunError( + "Got a %s response when uploading to %s: %s" + % (e.code, storage_url, e) + ) else: - raise ShotgunError("Unanticipated error occurred uploading to %s: %s" % (storage_url, e)) + raise ShotgunError( + "Unanticipated error occurred uploading to %s: %s" + % (storage_url, e) + ) except urllib.error.URLError as e: LOG.debug("Got a '%s' response. Waiting and retrying..." % e) time.sleep(float(attempt) * self.BACKOFF) @@ -4203,11 +4562,19 @@ def _complete_multipart_upload(self, upload_info, filename, etags): "filename": filename, "timestamp": upload_info["timestamp"], "upload_id": upload_info["upload_id"], - "etags": ",".join(etags) + "etags": ",".join(etags), } - url = urllib.parse.urlunparse((self.config.scheme, self.config.server, - "/upload/api_complete_multipart_upload", None, None, None)) + url = urllib.parse.urlunparse( + ( + self.config.scheme, + self.config.server, + "/upload/api_complete_multipart_upload", + None, + None, + None, + ) + ) result = self._send_form(url, params) # Response is of the form: 1\n or 0\n to indicate success or failure of the call. @@ -4283,8 +4650,11 @@ def _send_form(self, url, params): continue except urllib.error.HTTPError as e: if e.code == 500: - raise ShotgunError("Server encountered an internal error. " - "\n%s\n(%s)\n%s\n\n" % (url, self._sanitize_auth_params(params), e)) + raise ShotgunError( + "Server encountered an internal error. " + "\n%s\n(%s)\n%s\n\n" + % (url, self._sanitize_auth_params(params), e) + ) else: raise ShotgunError("Unanticipated error occurred %s" % (e)) @@ -4294,7 +4664,7 @@ def _send_form(self, url, params): class CACertsHTTPSConnection(http_client.HTTPConnection): - """" + """ " This class allows to create an HTTPS connection that uses the custom certificates passed in. """ @@ -4324,9 +4694,7 @@ def connect(self): self.sock = context.wrap_socket(self.sock) else: self.sock = ssl.wrap_socket( - self.sock, - ca_certs=self.__ca_certs, - cert_reqs=ssl.CERT_REQUIRED + self.sock, ca_certs=self.__ca_certs, cert_reqs=ssl.CERT_REQUIRED ) @@ -4352,6 +4720,7 @@ class FormPostHandler(urllib.request.BaseHandler): """ Handler for multipart form data """ + handler_order = urllib.request.HTTPHandler.handler_order - 10 # needs to run first def http_request(self, request): @@ -4370,7 +4739,9 @@ def http_request(self, request): else: params.append((key, value)) if not files: - data = sgutils.ensure_binary(urllib.parse.urlencode(params, True)) # sequencing on + data = sgutils.ensure_binary( + urllib.parse.urlencode(params, True) + ) # sequencing on else: boundary, data = self.encode(params, files) content_type = "multipart/form-data; boundary=%s" % boundary @@ -4392,7 +4763,7 @@ def encode(self, params, files, boundary=None, buffer=None): boundary = uuid.uuid4() if buffer is None: buffer = BytesIO() - for (key, value) in params: + for key, value in params: if not isinstance(value, str): # If value is not a string (e.g. int) cast to text value = str(value) @@ -4400,9 +4771,11 @@ def encode(self, params, files, boundary=None, buffer=None): key = sgutils.ensure_text(key) buffer.write(sgutils.ensure_binary("--%s\r\n" % boundary)) - buffer.write(sgutils.ensure_binary("Content-Disposition: form-data; name=\"%s\"" % key)) + buffer.write( + sgutils.ensure_binary('Content-Disposition: form-data; name="%s"' % key) + ) buffer.write(sgutils.ensure_binary("\r\n\r\n%s\r\n" % value)) - for (key, fd) in files: + for key, fd in files: # On Windows, it's possible that we were forced to open a file # with non-ascii characters as unicode. In that case, we need to # encode it as a utf-8 string to remove unicode from the equation. @@ -4416,7 +4789,7 @@ def encode(self, params, files, boundary=None, buffer=None): content_type = content_type or "application/octet-stream" file_size = os.fstat(fd.fileno())[stat.ST_SIZE] buffer.write(sgutils.ensure_binary("--%s\r\n" % boundary)) - c_dis = "Content-Disposition: form-data; name=\"%s\"; filename=\"%s\"%s" + c_dis = 'Content-Disposition: form-data; name="%s"; filename="%s"%s' content_disposition = c_dis % (key, filename, "\r\n") buffer.write(sgutils.ensure_binary(content_disposition)) buffer.write(sgutils.ensure_binary("Content-Type: %s\r\n" % content_type)) @@ -4438,10 +4811,7 @@ def _translate_filters(filters, filter_operator): """ Translate filters params into data structure expected by rpc call. """ - wrapped_filters = { - "filter_operator": filter_operator or "all", - "filters": filters - } + wrapped_filters = {"filter_operator": filter_operator or "all", "filters": filters} return _translate_filters_dict(wrapped_filters) @@ -4458,8 +4828,9 @@ def _translate_filters_dict(sg_filter): raise ShotgunError("Invalid filter_operator %s" % filter_operator) if not isinstance(sg_filter["filters"], (list, tuple)): - raise ShotgunError("Invalid filters, expected a list or a tuple, got %s" - % sg_filter["filters"]) + raise ShotgunError( + "Invalid filters, expected a list or a tuple, got %s" % sg_filter["filters"] + ) new_filters["conditions"] = _translate_filters_list(sg_filter["filters"]) @@ -4475,17 +4846,15 @@ def _translate_filters_list(filters): elif isinstance(sg_filter, dict): conditions.append(_translate_filters_dict(sg_filter)) else: - raise ShotgunError("Invalid filters, expected a list, tuple or dict, got %s" - % sg_filter) + raise ShotgunError( + "Invalid filters, expected a list, tuple or dict, got %s" % sg_filter + ) return conditions def _translate_filters_simple(sg_filter): - condition = { - "path": sg_filter[0], - "relation": sg_filter[1] - } + condition = {"path": sg_filter[0], "relation": sg_filter[1]} values = sg_filter[2:] if len(values) == 1 and isinstance(values[0], (list, tuple)): @@ -4523,7 +4892,7 @@ def _get_type_and_id_from_value(value): if isinstance(value, dict): return {"type": value["type"], "id": value["id"]} elif isinstance(value, list): - return [{"type": v["type"], "id": v["id"]} for v in value] + return [{"type": v["type"], "id": v["id"]} for v in value] except (KeyError, TypeError): LOG.debug(f"Could not optimize entity value {value}") diff --git a/tests/base.py b/tests/base.py index 4eaafb867..2820d495d 100644 --- a/tests/base.py +++ b/tests/base.py @@ -1,4 +1,5 @@ """Base class for Flow Production Tracking API tests.""" + import contextlib import os import random @@ -33,9 +34,9 @@ def skip(f): class TestBase(unittest.TestCase): - '''Base class for tests. + """Base class for tests. - Sets up mocking and database test data.''' + Sets up mocking and database test data.""" human_user = None project = None @@ -74,7 +75,7 @@ def setUpClass(cls): script_name=cls.config.script_name, api_key=cls.config.api_key ) - def setUp(self, auth_mode='ApiUser'): + def setUp(self, auth_mode="ApiUser"): # When running the tests from a pull request from a client, the Shotgun # site URL won't be set, so do not attempt to run the test. if not self.config.server_url: @@ -88,30 +89,38 @@ def setUp(self, auth_mode='ApiUser'): self.http_proxy = self.config.http_proxy self.session_uuid = self.config.session_uuid - if auth_mode == 'ApiUser': - self.sg = api.Shotgun(self.config.server_url, - self.config.script_name, - self.config.api_key, - http_proxy=self.config.http_proxy, - connect=self.connect) - elif auth_mode == 'HumanUser': - self.sg = api.Shotgun(self.config.server_url, - login=self.human_login, - password=self.human_password, - http_proxy=self.config.http_proxy, - connect=self.connect) - elif auth_mode == 'SessionToken': + if auth_mode == "ApiUser": + self.sg = api.Shotgun( + self.config.server_url, + self.config.script_name, + self.config.api_key, + http_proxy=self.config.http_proxy, + connect=self.connect, + ) + elif auth_mode == "HumanUser": + self.sg = api.Shotgun( + self.config.server_url, + login=self.human_login, + password=self.human_password, + http_proxy=self.config.http_proxy, + connect=self.connect, + ) + elif auth_mode == "SessionToken": # first make an instance based on script key/name so # we can generate a session token - sg = api.Shotgun(self.config.server_url, - http_proxy=self.config.http_proxy, - **self.auth_args) + sg = api.Shotgun( + self.config.server_url, + http_proxy=self.config.http_proxy, + **self.auth_args, + ) self.session_token = sg.get_session_token() # now log in using session token - self.sg = api.Shotgun(self.config.server_url, - session_token=self.session_token, - http_proxy=self.config.http_proxy, - connect=self.connect) + self.sg = api.Shotgun( + self.config.server_url, + session_token=self.session_token, + http_proxy=self.config.http_proxy, + connect=self.connect, + ) else: raise ValueError("Unknown value for auth_mode: %s" % auth_mode) @@ -123,7 +132,7 @@ def tearDown(self): class MockTestBase(TestBase): - '''Test base for tests mocking server interactions.''' + """Test base for tests mocking server interactions.""" def setUp(self): super(MockTestBase, self).setUp() @@ -132,23 +141,25 @@ def setUp(self): self._setup_mock_data() def _setup_mock(self, s3_status_code_error=503): - """Setup mocking on the ShotgunClient to stop it calling a live server - """ + """Setup mocking on the ShotgunClient to stop it calling a live server""" # Replace the function used to make the final call to the server # eaiser than mocking the http connection + response - self.sg._http_request = mock.Mock(spec=api.Shotgun._http_request, - return_value=((200, "OK"), {}, None)) + self.sg._http_request = mock.Mock( + spec=api.Shotgun._http_request, return_value=((200, "OK"), {}, None) + ) # Replace the function used to make the final call to the S3 server, and simulate # the exception HTTPError raised with 503 status errors - self.sg._make_upload_request = mock.Mock(spec=api.Shotgun._make_upload_request, - side_effect = urllib.error.HTTPError( - "url", - s3_status_code_error, - "The server is currently down or to busy to reply." - "Please try again later.", - {}, - None - )) + self.sg._make_upload_request = mock.Mock( + spec=api.Shotgun._make_upload_request, + side_effect=urllib.error.HTTPError( + "url", + s3_status_code_error, + "The server is currently down or to busy to reply." + "Please try again later.", + {}, + None, + ), + ) # also replace the function that is called to get the http connection # to avoid calling the server. OK to return a mock as we will not use # it @@ -160,8 +171,9 @@ def _setup_mock(self, s3_status_code_error=503): self.sg._get_connection = mock.Mock(return_value=self.mock_conn) # create the server caps directly to say we have the correct version - self.sg._server_caps = ServerCapabilities(self.sg.config.server, - {"version": [2, 4, 0]}) + self.sg._server_caps = ServerCapabilities( + self.sg.config.server, {"version": [2, 4, 0]} + ) # prevent waiting for backoff self.sg.BACKOFF = 0 @@ -177,24 +189,22 @@ def _mock_http(self, data, headers=None, status=None): if not isinstance(data, str): if six.PY2: - data = json.dumps( - data, - ensure_ascii=False, - encoding="utf-8" - ) + data = json.dumps(data, ensure_ascii=False, encoding="utf-8") else: data = json.dumps( data, ensure_ascii=False, ) - resp_headers = {'cache-control': 'no-cache', - 'connection': 'close', - 'content-length': (data and str(len(data))) or 0, - 'content-type': 'application/json; charset=utf-8', - 'date': 'Wed, 13 Apr 2011 04:18:58 GMT', - 'server': 'Apache/2.2.3 (CentOS)', - 'status': '200 OK'} + resp_headers = { + "cache-control": "no-cache", + "connection": "close", + "content-length": (data and str(len(data))) or 0, + "content-type": "application/json; charset=utf-8", + "date": "Wed, 13 Apr 2011 04:18:58 GMT", + "server": "Apache/2.2.3 (CentOS)", + "status": "200 OK", + } if headers: resp_headers.update(headers) @@ -220,42 +230,40 @@ def _assert_http_method(self, method, params, check_auth=True): self.assertEqual(self.api_key, auth["script_key"]) if params: - rpc_args = arg_params[len(arg_params)-1] + rpc_args = arg_params[len(arg_params) - 1] self.assertEqual(params, rpc_args) def _setup_mock_data(self): - self.human_user = {'id': 1, - 'login': self.config.human_login, - 'type': 'HumanUser'} - self.project = {'id': 2, - 'name': self.config.project_name, - 'type': 'Project'} - self.shot = {'id': 3, - 'code': self.config.shot_code, - 'type': 'Shot'} - self.asset = {'id': 4, - 'code': self.config.asset_code, - 'type': 'Asset'} - self.version = {'id': 5, - 'code': self.config.version_code, - 'type': 'Version'} - self.playlist = {'id': 7, - 'code': self.config.playlist_code, - 'type': 'Playlist'} + self.human_user = { + "id": 1, + "login": self.config.human_login, + "type": "HumanUser", + } + self.project = {"id": 2, "name": self.config.project_name, "type": "Project"} + self.shot = {"id": 3, "code": self.config.shot_code, "type": "Shot"} + self.asset = {"id": 4, "code": self.config.asset_code, "type": "Asset"} + self.version = {"id": 5, "code": self.config.version_code, "type": "Version"} + self.playlist = {"id": 7, "code": self.config.playlist_code, "type": "Playlist"} class LiveTestBase(TestBase): - '''Test base for tests relying on connection to server.''' + """Test base for tests relying on connection to server.""" def setUp(self, auth_mode=None): if not auth_mode: - auth_mode = 'HumanUser' if self.config.jenkins else 'ApiUser' + auth_mode = "HumanUser" if self.config.jenkins else "ApiUser" super(LiveTestBase, self).setUp(auth_mode) - if self.sg.server_caps.version and \ - self.sg.server_caps.version >= (3, 3, 0) and \ - (self.sg.server_caps.host.startswith('0.0.0.0') or - self.sg.server_caps.host.startswith('127.0.0.1')): - self.server_address = re.sub('^0.0.0.0|127.0.0.1', 'localhost', self.sg.server_caps.host) + if ( + self.sg.server_caps.version + and self.sg.server_caps.version >= (3, 3, 0) + and ( + self.sg.server_caps.host.startswith("0.0.0.0") + or self.sg.server_caps.host.startswith("127.0.0.1") + ) + ): + self.server_address = re.sub( + "^0.0.0.0|127.0.0.1", "localhost", self.sg.server_caps.host + ) else: self.server_address = self.sg.server_caps.host @@ -279,73 +287,75 @@ def setUpClass(cls): cls.config.server_url, **cls.auth_args, ) - cls.sg_version = tuple(sg.info()['version'][:3]) + cls.sg_version = tuple(sg.info()["version"][:3]) cls._setup_db(cls.config, sg) @classmethod def _setup_db(cls, config, sg): - data = {'name': cls.config.project_name} - cls.project = _find_or_create_entity(sg, 'Project', data) - - data = {'name': cls.config.human_name, - 'login': cls.config.human_login, - 'password_proxy': cls.config.human_password} + data = {"name": cls.config.project_name} + cls.project = _find_or_create_entity(sg, "Project", data) + + data = { + "name": cls.config.human_name, + "login": cls.config.human_login, + "password_proxy": cls.config.human_password, + } if cls.sg_version >= (3, 0, 0): - data['locked_until'] = None - - cls.human_user = _find_or_create_entity(sg, 'HumanUser', data) - - data = {'code': cls.config.asset_code, - 'project': cls.project} - keys = ['code'] - cls.asset = _find_or_create_entity(sg, 'Asset', data, keys) - - data = {'project': cls.project, - 'code': cls.config.version_code, - 'entity': cls.asset, - 'user': cls.human_user, - 'sg_frames_aspect_ratio': 13.3, - 'frame_count': 33} - keys = ['code', 'project'] - cls.version = _find_or_create_entity(sg, 'Version', data, keys) - - keys = ['code', 'project'] - data = {'code': cls.config.shot_code, - 'project': cls.project} - cls.shot = _find_or_create_entity(sg, 'Shot', data, keys) - - keys = ['project', 'user'] - data = {'project': cls.project, - 'user': cls.human_user, - 'content': 'anything'} - cls.note = _find_or_create_entity(sg, 'Note', data, keys) - - keys = ['code', 'project'] - data = {'project': cls.project, - 'code': cls.config.playlist_code} - cls.playlist = _find_or_create_entity(sg, 'Playlist', data, keys) - - keys = ['code', 'entity_type'] - data = {'code': 'wrapper test step', - 'entity_type': 'Shot'} - cls.step = _find_or_create_entity(sg, 'Step', data, keys) - - keys = ['project', 'entity', 'content'] - data = {'project': cls.project, - 'entity': cls.asset, - 'content': cls.config.task_content, - 'color': 'Black', - 'due_date': '1968-10-13', - 'task_assignees': [cls.human_user], - 'sg_status_list': 'ip'} - cls.task = _find_or_create_entity(sg, 'Task', data, keys) - - keys = ['code'] - data = {'code': 'api wrapper test storage', - 'mac_path': 'nowhere', - 'windows_path': 'nowhere', - 'linux_path': 'nowhere'} - cls.local_storage = _find_or_create_entity(sg, 'LocalStorage', data, keys) + data["locked_until"] = None + + cls.human_user = _find_or_create_entity(sg, "HumanUser", data) + + data = {"code": cls.config.asset_code, "project": cls.project} + keys = ["code"] + cls.asset = _find_or_create_entity(sg, "Asset", data, keys) + + data = { + "project": cls.project, + "code": cls.config.version_code, + "entity": cls.asset, + "user": cls.human_user, + "sg_frames_aspect_ratio": 13.3, + "frame_count": 33, + } + keys = ["code", "project"] + cls.version = _find_or_create_entity(sg, "Version", data, keys) + + keys = ["code", "project"] + data = {"code": cls.config.shot_code, "project": cls.project} + cls.shot = _find_or_create_entity(sg, "Shot", data, keys) + + keys = ["project", "user"] + data = {"project": cls.project, "user": cls.human_user, "content": "anything"} + cls.note = _find_or_create_entity(sg, "Note", data, keys) + + keys = ["code", "project"] + data = {"project": cls.project, "code": cls.config.playlist_code} + cls.playlist = _find_or_create_entity(sg, "Playlist", data, keys) + + keys = ["code", "entity_type"] + data = {"code": "wrapper test step", "entity_type": "Shot"} + cls.step = _find_or_create_entity(sg, "Step", data, keys) + + keys = ["project", "entity", "content"] + data = { + "project": cls.project, + "entity": cls.asset, + "content": cls.config.task_content, + "color": "Black", + "due_date": "1968-10-13", + "task_assignees": [cls.human_user], + "sg_status_list": "ip", + } + cls.task = _find_or_create_entity(sg, "Task", data, keys) + + keys = ["code"] + data = { + "code": "api wrapper test storage", + "mac_path": "nowhere", + "windows_path": "nowhere", + "linux_path": "nowhere", + } + cls.local_storage = _find_or_create_entity(sg, "LocalStorage", data, keys) @contextlib.contextmanager def gen_entity(self, entity_type, **kwargs): @@ -360,7 +370,7 @@ def gen_entity(self, entity_type, **kwargs): if "password_proxy" not in kwargs: kwargs["password_proxy"] = self.config.human_password - item_rnd = random.randrange(100,999) + item_rnd = random.randrange(100, 999) for k in kwargs: if isinstance(kwargs[k], str): kwargs[k] = kwargs[k].format(rnd=item_rnd) @@ -372,13 +382,20 @@ def gen_entity(self, entity_type, **kwargs): rv = self.sg.delete(entity_type, entity["id"]) assert rv == True - def find_one_await_thumbnail(self, entity_type, filters, fields=["image"], thumbnail_field_name="image", **kwargs): + def find_one_await_thumbnail( + self, + entity_type, + filters, + fields=["image"], + thumbnail_field_name="image", + **kwargs + ): attempts = 0 while attempts < THUMBNAIL_MAX_ATTEMPTS: result = self.sg.find_one(entity_type, filters, fields=fields, **kwargs) if TRANSIENT_IMAGE_PATH in result.get(thumbnail_field_name, ""): return result - + time.sleep(THUMBNAIL_RETRY_INTERVAL) attempts += 1 else: @@ -387,43 +404,55 @@ def find_one_await_thumbnail(self, entity_type, filters, fields=["image"], thumb class HumanUserAuthLiveTestBase(LiveTestBase): - ''' + """ Test base for relying on a Shotgun connection authenticate through the configured login/password pair. - ''' + """ def setUp(self): - super(HumanUserAuthLiveTestBase, self).setUp('HumanUser') + super(HumanUserAuthLiveTestBase, self).setUp("HumanUser") class SessionTokenAuthLiveTestBase(LiveTestBase): - ''' + """ Test base for relying on a Shotgun connection authenticate through the configured session_token parameter. - ''' + """ def setUp(self): - super(SessionTokenAuthLiveTestBase, self).setUp('SessionToken') + super(SessionTokenAuthLiveTestBase, self).setUp("SessionToken") class SgTestConfig(object): - '''Reads test config and holds values''' + """Reads test config and holds values""" def __init__(self): for key in self.config_keys(): # Look for any environment variables that match our test # configuration naming of "SG_{KEY}". Default is None. - value = os.environ.get('SG_%s' % (str(key).upper())) - if key in ['mock']: - value = (value is None) or (str(value).lower() in ['true', '1']) + value = os.environ.get("SG_%s" % (str(key).upper())) + if key in ["mock"]: + value = (value is None) or (str(value).lower() in ["true", "1"]) setattr(self, key, value) def config_keys(self): return [ - 'api_key', 'asset_code', 'http_proxy', 'human_login', 'human_name', - 'human_password', 'mock', 'project_name', 'script_name', - 'server_url', 'session_uuid', 'shot_code', 'task_content', - 'version_code', 'playlist_code', 'jenkins' + "api_key", + "asset_code", + "http_proxy", + "human_login", + "human_name", + "human_password", + "mock", + "project_name", + "script_name", + "server_url", + "session_uuid", + "shot_code", + "task_content", + "version_code", + "playlist_code", + "jenkins", ] def read_config(self, config_path): @@ -439,7 +468,7 @@ def read_config(self, config_path): def _find_or_create_entity(sg, entity_type, data, identifyiers=None): - '''Finds or creates entities. + """Finds or creates entities. @params: sg - shogun_json.Shotgun instance entity_type - entity type @@ -447,11 +476,11 @@ def _find_or_create_entity(sg, entity_type, data, identifyiers=None): identifyiers -list of subset of keys from data which should be used to uniquely identity the entity @returns dicitonary of the entity values - ''' - identifyiers = identifyiers or ['name'] + """ + identifyiers = identifyiers or ["name"] fields = list(data.keys()) - filters = [[key, 'is', data[key]] for key in identifyiers] + filters = [[key, "is", data[key]] for key in identifyiers] entity = sg.find_one(entity_type, filters, fields=fields) entity = entity or sg.create(entity_type, data, return_fields=fields) - assert(entity) + assert entity return entity diff --git a/tests/ci_requirements.txt b/tests/ci_requirements.txt index 92189202a..5c2074965 100644 --- a/tests/ci_requirements.txt +++ b/tests/ci_requirements.txt @@ -8,14 +8,14 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. +coverage coveralls==1.1 -nose==1.3.7 -nose-exclude==0.5.0 # Don't restrict flake8 version, since we install this in CI against Python 2.6, # where flake8 has discontinued support for newer releases. On Python 2.7 and # Python 3.7, linting has been performed with flake8 3.7.8 flake8 +nose==1.3.7 +nose-exclude==0.5.0 pytest pytest-azurepipelines -coverage pytest-coverage diff --git a/tests/mock.py b/tests/mock.py index 456c02594..736571c64 100644 --- a/tests/mock.py +++ b/tests/mock.py @@ -14,16 +14,16 @@ __all__ = ( - 'Mock', - 'MagicMock', - 'mocksignature', - 'patch', - 'patch_object', - 'sentinel', - 'DEFAULT' + "Mock", + "MagicMock", + "mocksignature", + "patch", + "patch_object", + "sentinel", + "DEFAULT", ) -__version__ = '0.7.0' +__version__ = "0.7.0" __unittest = True @@ -48,8 +48,10 @@ def inner(f): f.__doc__ = original.__doc__ f.__module__ = original.__module__ return f + return inner + try: unicode except NameError: @@ -65,18 +67,19 @@ def inner(f): inPy3k = sys.version_info[0] == 3 if inPy3k: - self = '__self__' + self = "__self__" else: - self = 'im_self' + self = "im_self" # getsignature and mocksignature heavily "inspired" by # the decorator module: http://pypi.python.org/pypi/decorator/ # by Michele Simionato + def _getsignature(func, skipfirst): if inspect is None: - raise ImportError('inspect module not available') + raise ImportError("inspect module not available") if inspect.isclass(func): func = func.__init__ @@ -92,15 +95,16 @@ def _getsignature(func, skipfirst): regargs = regargs[1:] _msg = "_mock_ is a reserved argument name, can't mock signatures using _mock_" - assert '_mock_' not in regargs, _msg + assert "_mock_" not in regargs, _msg if varargs is not None: - assert '_mock_' not in varargs, _msg + assert "_mock_" not in varargs, _msg if varkwargs is not None: - assert '_mock_' not in varkwargs, _msg + assert "_mock_" not in varkwargs, _msg if skipfirst: regargs = regargs[1:] - signature = inspect.formatargspec(regargs, varargs, varkwargs, defaults, - formatvalue=lambda value: "") + signature = inspect.formatargspec( + regargs, varargs, varkwargs, defaults, formatvalue=lambda value: "" + ) return signature[1:-1], func @@ -138,9 +142,7 @@ def mocksignature(func, mock=None, skipfirst=False): if mock is None: mock = Mock() signature, func = _getsignature(func, skipfirst) - src = "lambda %(signature)s: _mock_(%(signature)s)" % { - 'signature': signature - } + src = "lambda %(signature)s: _mock_(%(signature)s)" % {"signature": signature} funcopy = eval(src, dict(_mock_=mock)) _copy_func_details(func, funcopy) @@ -149,11 +151,12 @@ def mocksignature(func, mock=None, skipfirst=False): def _is_magic(name): - return '__%s__' % name[2:-2] == name + return "__%s__" % name[2:-2] == name class SentinelObject(object): "A unique, named, sentinel object." + def __init__(self, name): self.name = name @@ -163,11 +166,12 @@ def __repr__(self): class Sentinel(object): """Access attributes to return a named object, usable as a sentinel.""" + def __init__(self): self._sentinels = {} def __getattr__(self, name): - if name == '__bases__': + if name == "__bases__": # Without this help(mock) raises an exception raise AttributeError return self._sentinels.setdefault(name, SentinelObject(name)) @@ -180,6 +184,8 @@ def __getattr__(self, name): class OldStyleClass: pass + + ClassType = type(OldStyleClass) @@ -241,16 +247,24 @@ class or instance) that acts as the specification for the mock object. If mock. This can be useful for debugging. The name is propagated to child mocks. """ + def __new__(cls, *args, **kw): # every instance has its own class # so we can create magic methods on the # class without stomping on other mocks - new = type(cls.__name__, (cls,), {'__doc__': cls.__doc__}) + new = type(cls.__name__, (cls,), {"__doc__": cls.__doc__}) return object.__new__(new) - - def __init__(self, spec=None, side_effect=None, return_value=DEFAULT, - wraps=None, name=None, spec_set=None, parent=None): + def __init__( + self, + spec=None, + side_effect=None, + return_value=DEFAULT, + wraps=None, + name=None, + spec_set=None, + parent=None, + ): self._parent = parent self._name = name _spec_class = None @@ -275,14 +289,12 @@ def __init__(self, spec=None, side_effect=None, return_value=DEFAULT, self.reset_mock() - @property def __class__(self): if self._spec_class is None: return type(self) return self._spec_class - def reset_mock(self): "Restore the mock object to its initial state." self.called = False @@ -296,7 +308,6 @@ def reset_mock(self): if not self._return_value is self: self._return_value.reset_mock() - def __get_return_value(self): if self._return_value is DEFAULT: self._return_value = self._get_child_mock() @@ -306,9 +317,7 @@ def __set_return_value(self, value): self._return_value = value __return_value_doc = "The value to be returned when the mock is called." - return_value = property(__get_return_value, __set_return_value, - __return_value_doc) - + return_value = property(__get_return_value, __set_return_value, __return_value_doc) def __call__(self, *args, **kwargs): self.called = True @@ -322,14 +331,16 @@ def __call__(self, *args, **kwargs): parent.method_calls.append(callargs((name, args, kwargs))) if parent._parent is None: break - name = parent._name + '.' + name + name = parent._name + "." + name parent = parent._parent ret_val = DEFAULT if self.side_effect is not None: - if (isinstance(self.side_effect, BaseException) or - isinstance(self.side_effect, class_types) and - issubclass(self.side_effect, BaseException)): + if ( + isinstance(self.side_effect, BaseException) + or isinstance(self.side_effect, class_types) + and issubclass(self.side_effect, BaseException) + ): raise self.side_effect ret_val = self.side_effect(*args, **kwargs) @@ -342,9 +353,8 @@ def __call__(self, *args, **kwargs): ret_val = self.return_value return ret_val - def __getattr__(self, name): - if name == '_methods': + if name == "_methods": raise AttributeError(name) elif self._methods is not None: if name not in self._methods or name in _all_magics: @@ -356,49 +366,57 @@ def __getattr__(self, name): wraps = None if self._wraps is not None: wraps = getattr(self._wraps, name) - self._children[name] = self._get_child_mock(parent=self, name=name, wraps=wraps) + self._children[name] = self._get_child_mock( + parent=self, name=name, wraps=wraps + ) return self._children[name] - def __repr__(self): if self._name is None and self._spec_class is None: return object.__repr__(self) - name_string = '' - spec_string = '' + name_string = "" + spec_string = "" if self._name is not None: + def get_name(name): if name is None: - return 'mock' + return "mock" return name + parent = self._parent name = self._name while parent is not None: - name = get_name(parent._name) + '.' + name + name = get_name(parent._name) + "." + name parent = parent._parent - name_string = ' name=%r' % name + name_string = " name=%r" % name if self._spec_class is not None: - spec_string = ' spec=%r' + spec_string = " spec=%r" if self._spec_set: - spec_string = ' spec_set=%r' + spec_string = " spec_set=%r" spec_string = spec_string % self._spec_class.__name__ - return "<%s%s%s id='%s'>" % (type(self).__name__, - name_string, - spec_string, - id(self)) - + return "<%s%s%s id='%s'>" % ( + type(self).__name__, + name_string, + spec_string, + id(self), + ) def __setattr__(self, name, value): - if not 'method_calls' in self.__dict__: + if not "method_calls" in self.__dict__: # allow all attribute setting until initialisation is complete return object.__setattr__(self, name, value) - if (self._spec_set and self._methods is not None and name not in - self._methods and name not in self.__dict__ and - name != 'return_value'): + if ( + self._spec_set + and self._methods is not None + and name not in self._methods + and name not in self.__dict__ + and name != "return_value" + ): raise AttributeError("Mock object has no attribute '%s'" % name) if name in _unsupported_magics: - msg = 'Attempting to set unsupported magic method %r.' % name + msg = "Attempting to set unsupported magic method %r." % name raise AttributeError(msg) elif name in _all_magics: if self._methods is not None and name not in self._methods: @@ -413,13 +431,11 @@ def __setattr__(self, name, value): setattr(type(self), name, value) return object.__setattr__(self, name, value) - def __delattr__(self, name): if name in _all_magics and name in type(self).__dict__: delattr(type(self), name) return object.__delattr__(self, name) - def assert_called_with(self, *args, **kwargs): """ assert that the mock was called with the specified arguments. @@ -428,31 +444,27 @@ def assert_called_with(self, *args, **kwargs): different to the last call to the mock. """ if self.call_args is None: - raise AssertionError('Expected: %s\nNot called' % ((args, kwargs),)) + raise AssertionError("Expected: %s\nNot called" % ((args, kwargs),)) if not self.call_args == (args, kwargs): raise AssertionError( - 'Expected: %s\nCalled with: %s' % ((args, kwargs), self.call_args) + "Expected: %s\nCalled with: %s" % ((args, kwargs), self.call_args) ) - def assert_called_once_with(self, *args, **kwargs): """ assert that the mock was called exactly once and with the specified arguments. """ if not self.call_count == 1: - msg = ("Expected to be called once. Called %s times." % - self.call_count) + msg = "Expected to be called once. Called %s times." % self.call_count raise AssertionError(msg) return self.assert_called_with(*args, **kwargs) - def _get_child_mock(self, **kw): klass = type(self).__mro__[1] return klass(**kw) - class callargs(tuple): """ A tuple for holding the results of a call to a mock, either in the form @@ -465,6 +477,7 @@ class callargs(tuple): callargs('name', (1,), {}) == ('name', (1,)) callargs((), {'a': 'b'}) == ({'a': 'b'},) """ + def __eq__(self, other): if len(self) == 3: if other[0] != self[0]: @@ -499,7 +512,7 @@ def _dot_lookup(thing, comp, import_path): def _importer(target): - components = target.split('.') + components = target.split(".") import_path = components.pop(0) thing = __import__(import_path) @@ -510,8 +523,7 @@ def _importer(target): class _patch(object): - def __init__(self, target, attribute, new, spec, create, - mocksignature, spec_set): + def __init__(self, target, attribute, new, spec, create, mocksignature, spec_set): self.target = target self.attribute = attribute self.new = new @@ -521,11 +533,16 @@ def __init__(self, target, attribute, new, spec, create, self.mocksignature = mocksignature self.spec_set = spec_set - def copy(self): - return _patch(self.target, self.attribute, self.new, self.spec, - self.create, self.mocksignature, self.spec_set) - + return _patch( + self.target, + self.attribute, + self.new, + self.spec, + self.create, + self.mocksignature, + self.spec_set, + ) def __call__(self, func): if isinstance(func, class_types): @@ -533,7 +550,6 @@ def __call__(self, func): else: return self.decorate_callable(func) - def decorate_class(self, klass): for attr in dir(klass): attr_value = getattr(klass, attr) @@ -541,9 +557,8 @@ def decorate_class(self, klass): setattr(klass, attr, self.copy()(attr_value)) return klass - def decorate_callable(self, func): - if hasattr(func, 'patchings'): + if hasattr(func, "patchings"): func.patchings.append(self) return func @@ -559,17 +574,17 @@ def patched(*args, **keywargs): try: return func(*args, **keywargs) finally: - for patching in reversed(getattr(patched, 'patchings', [])): + for patching in reversed(getattr(patched, "patchings", [])): patching.__exit__() patched.patchings = [self] - if hasattr(func, 'func_code'): + if hasattr(func, "func_code"): # not in Python 3 - patched.compat_co_firstlineno = getattr(func, "compat_co_firstlineno", - func.func_code.co_firstlineno) + patched.compat_co_firstlineno = getattr( + func, "compat_co_firstlineno", func.func_code.co_firstlineno + ) return patched - def get_original(self): target = self.target name = self.attribute @@ -588,7 +603,6 @@ def get_original(self): raise AttributeError("%s does not have the attribute %r" % (target, name)) return original, local - def __enter__(self): """Perform the patch.""" new, spec, spec_set = self.new, self.spec, self.spec_set @@ -617,7 +631,6 @@ def __enter__(self): setattr(self.target, self.attribute, new_attr) return new - def __exit__(self, *_): """Undo the patch.""" if self.is_local and self.temp_original is not DEFAULT: @@ -635,8 +648,15 @@ def __exit__(self, *_): stop = __exit__ -def _patch_object(target, attribute, new=DEFAULT, spec=None, create=False, - mocksignature=False, spec_set=None): +def _patch_object( + target, + attribute, + new=DEFAULT, + spec=None, + create=False, + mocksignature=False, + spec_set=None, +): """ patch.object(target, attribute, new=DEFAULT, spec=None, create=False, mocksignature=False, spec_set=None) @@ -647,18 +667,18 @@ def _patch_object(target, attribute, new=DEFAULT, spec=None, create=False, Arguments new, spec, create, mocksignature and spec_set have the same meaning as for patch. """ - return _patch(target, attribute, new, spec, create, mocksignature, - spec_set) + return _patch(target, attribute, new, spec, create, mocksignature, spec_set) def patch_object(*args, **kwargs): "A deprecated form of patch.object(...)" - warnings.warn(('Please use patch.object instead.'), DeprecationWarning, 2) + warnings.warn(("Please use patch.object instead."), DeprecationWarning, 2) return _patch_object(*args, **kwargs) -def patch(target, new=DEFAULT, spec=None, create=False, - mocksignature=False, spec_set=None): +def patch( + target, new=DEFAULT, spec=None, create=False, mocksignature=False, spec_set=None +): """ ``patch`` acts as a function decorator, class decorator or a context manager. Inside the body of the function or with statement, the ``target`` @@ -707,10 +727,9 @@ def patch(target, new=DEFAULT, spec=None, create=False, use-cases. """ try: - target, attribute = target.rsplit('.', 1) + target, attribute = target.rsplit(".", 1) except (TypeError, ValueError): - raise TypeError("Need a valid target to patch. You supplied: %r" % - (target,)) + raise TypeError("Need a valid target to patch. You supplied: %r" % (target,)) target = _importer(target) return _patch(target, attribute, new, spec, create, mocksignature, spec_set) @@ -743,10 +762,10 @@ def __init__(self, in_dict, values=(), clear=False): self.clear = clear self._original = None - def __call__(self, f): if isinstance(f, class_types): return self.decorate_class(f) + @wraps(f) def _inner(*args, **kw): self._patch_dict() @@ -757,7 +776,6 @@ def _inner(*args, **kw): return _inner - def decorate_class(self, klass): for attr in dir(klass): attr_value = getattr(klass, attr) @@ -767,12 +785,10 @@ def decorate_class(self, klass): setattr(klass, attr, decorated) return klass - def __enter__(self): """Patch the dict.""" self._patch_dict() - def _patch_dict(self): """Unpatch the dict.""" values = self.values @@ -799,7 +815,6 @@ def _patch_dict(self): for key in values: in_dict[key] = values[key] - def _unpatch_dict(self): in_dict = self.in_dict original = self._original @@ -812,7 +827,6 @@ def _unpatch_dict(self): for key in original: in_dict[key] = original[key] - def __exit__(self, *args): self._unpatch_dict() return False @@ -846,71 +860,97 @@ def _clear_dict(in_dict): ) numerics = "add sub mul div truediv floordiv mod lshift rshift and xor or pow " -inplace = ' '.join('i%s' % n for n in numerics.split()) -right = ' '.join('r%s' % n for n in numerics.split()) -extra = '' +inplace = " ".join("i%s" % n for n in numerics.split()) +right = " ".join("r%s" % n for n in numerics.split()) +extra = "" if inPy3k: - extra = 'bool next ' + extra = "bool next " else: - extra = 'unicode long nonzero oct hex ' + extra = "unicode long nonzero oct hex " # __truediv__ and __rtruediv__ not available in Python 3 either # not including __prepare__, __instancecheck__, __subclasscheck__ # (as they are metaclass methods) # __del__ is not supported at all as it causes problems if it exists -_non_defaults = set('__%s__' % method for method in [ - 'cmp', 'getslice', 'setslice', 'coerce', 'subclasses', - 'dir', 'format', 'get', 'set', 'delete', 'reversed', - 'missing', 'reduce', 'reduce_ex', 'getinitargs', - 'getnewargs', 'getstate', 'setstate', 'getformat', - 'setformat', 'repr' -]) +_non_defaults = set( + "__%s__" % method + for method in [ + "cmp", + "getslice", + "setslice", + "coerce", + "subclasses", + "dir", + "format", + "get", + "set", + "delete", + "reversed", + "missing", + "reduce", + "reduce_ex", + "getinitargs", + "getnewargs", + "getstate", + "setstate", + "getformat", + "setformat", + "repr", + ] +) def _get_method(name, func): "Turns a callable object (like a mock) into a real function" + def method(self, *args, **kw): return func(self, *args, **kw) + method.__name__ = name return method _magics = set( - '__%s__' % method for method in - ' '.join([magic_methods, numerics, inplace, right, extra]).split() + "__%s__" % method + for method in " ".join([magic_methods, numerics, inplace, right, extra]).split() ) _all_magics = _magics | _non_defaults -_unsupported_magics = set([ - '__getattr__', '__setattr__', - '__init__', '__new__', '__prepare__' - '__instancecheck__', '__subclasscheck__', - '__del__' -]) +_unsupported_magics = set( + [ + "__getattr__", + "__setattr__", + "__init__", + "__new__", + "__prepare__" "__instancecheck__", + "__subclasscheck__", + "__del__", + ] +) _calculate_return_value = { - '__hash__': lambda self: object.__hash__(self), - '__str__': lambda self: object.__str__(self), - '__sizeof__': lambda self: object.__sizeof__(self), - '__unicode__': lambda self: unicode(object.__str__(self)), + "__hash__": lambda self: object.__hash__(self), + "__str__": lambda self: object.__str__(self), + "__sizeof__": lambda self: object.__sizeof__(self), + "__unicode__": lambda self: unicode(object.__str__(self)), } _return_values = { - '__int__': 1, - '__contains__': False, - '__len__': 0, - '__iter__': iter([]), - '__exit__': False, - '__complex__': 1j, - '__float__': 1.0, - '__bool__': True, - '__nonzero__': True, - '__oct__': '1', - '__hex__': '0x1', - '__long__': long(1), - '__index__': 1, + "__int__": 1, + "__contains__": False, + "__len__": 0, + "__iter__": iter([]), + "__exit__": False, + "__complex__": 1j, + "__float__": 1.0, + "__bool__": True, + "__nonzero__": True, + "__oct__": "1", + "__hex__": "0x1", + "__long__": long(1), + "__index__": 1, } @@ -938,6 +978,7 @@ class MagicMock(Mock): Attributes and the return value of a `MagicMock` will also be `MagicMocks`. """ + def __init__(self, *args, **kw): Mock.__init__(self, *args, **kw) diff --git a/tests/test_api.py b/tests/test_api.py index 9fbc7a678..0e611316a 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -45,7 +45,7 @@ class TestShotgunApi(base.LiveTestBase): def setUp(self): super(TestShotgunApi, self).setUp() # give note unicode content - self.sg.update('Note', self.note['id'], {'content': u'La Pe\xf1a'}) + self.sg.update("Note", self.note["id"], {"content": "La Pe\xf1a"}) def test_info(self): """Called info""" @@ -55,9 +55,8 @@ def test_info(self): def test_server_dates(self): """Pass datetimes to the server""" # TODO check results - t = {'project': self.project, - 'start_date': datetime.date.today()} - self.sg.create('Task', t, ['content', 'sg_status_list']) + t = {"project": self.project, "start_date": datetime.date.today()} + self.sg.create("Task", t, ["content", "sg_status_list"]) def test_batch(self): """Batched create, update, delete""" @@ -66,39 +65,30 @@ def test_batch(self): { "request_type": "create", "entity_type": "Shot", - "data": { - "code": "New Shot 5", - "project": self.project - } + "data": {"code": "New Shot 5", "project": self.project}, }, { "request_type": "update", "entity_type": "Shot", - "entity_id": self.shot['id'], - "data": { - "code": "Changed 1" - } - } + "entity_id": self.shot["id"], + "data": {"code": "Changed 1"}, + }, ] new_shot, updated_shot = self.sg.batch(requests) - self.assertEqual(self.shot['id'], updated_shot["id"]) + self.assertEqual(self.shot["id"], updated_shot["id"]) self.assertTrue(new_shot.get("id")) new_shot_id = new_shot["id"] requests = [ - { - "request_type": "delete", - "entity_type": "Shot", - "entity_id": new_shot_id - }, + {"request_type": "delete", "entity_type": "Shot", "entity_id": new_shot_id}, { "request_type": "update", "entity_type": "Shot", - "entity_id": self.shot['id'], - "data": {"code": self.shot['code']} - } + "entity_id": self.shot["id"], + "data": {"code": self.shot["code"]}, + }, ] result = self.sg.batch(requests)[0] @@ -112,12 +102,12 @@ def test_empty_batch(self): def test_create_update_delete(self): """Called create, update, delete, revive""" data = { - 'project': self.project, - 'code': 'JohnnyApple_Design01_FaceFinal', - 'description': 'fixed rig per director final notes', - 'sg_status_list': 'rev', - 'entity': self.asset, - 'user': self.human_user + "project": self.project, + "code": "JohnnyApple_Design01_FaceFinal", + "description": "fixed rig per director final notes", + "sg_status_list": "rev", + "entity": self.asset, + "user": self.human_user, } version = self.sg.create("Version", data, return_fields=["id"]) @@ -126,9 +116,7 @@ def test_create_update_delete(self): # TODO check results more thoroughly # TODO: test returned fields are requested fields - data = data = { - "description": "updated test" - } + data = data = {"description": "updated test"} version = self.sg.update("Version", version["id"], data) self.assertTrue(isinstance(version, dict)) self.assertTrue("id" in version) @@ -144,9 +132,9 @@ def test_create_update_delete(self): self.assertEqual(False, rv) def test_last_accessed(self): - page = self.sg.find('Page', [], fields=['last_accessed'], limit=1) - self.assertEqual("Page", page[0]['type']) - self.assertEqual(datetime.datetime, type(page[0]['last_accessed'])) + page = self.sg.find("Page", [], fields=["last_accessed"], limit=1) + self.assertEqual("Page", page[0]["type"]) + self.assertEqual(datetime.datetime, type(page[0]["last_accessed"])) def test_get_session_token(self): """Got session UUID""" @@ -158,18 +146,23 @@ def test_upload_download(self): """Upload and download an attachment tests""" # upload / download only works against a live server because it does # not use the standard http interface - if 'localhost' in self.server_url: + if "localhost" in self.server_url: print("upload / down tests skipped for localhost") return this_dir, _ = os.path.split(__file__) - path = os.path.abspath(os.path.expanduser( - os.path.join(this_dir, "sg_logo.jpg"))) + path = os.path.abspath( + os.path.expanduser(os.path.join(this_dir, "sg_logo.jpg")) + ) size = os.stat(path).st_size - attach_id = self.sg.upload("Version", - self.version['id'], path, 'sg_uploaded_movie', - tag_list="monkeys, everywhere, send, help") + attach_id = self.sg.upload( + "Version", + self.version["id"], + path, + "sg_uploaded_movie", + tag_list="monkeys, everywhere, send, help", + ) # test download with attachment_id attach_file = self.sg.download_attachment(attach_id) @@ -186,23 +179,30 @@ def test_upload_download(self): self.assertEqual(orig_file, attach_file) # test download with attachment_id (write to disk) - file_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "sg_logo_download.jpg") + file_path = os.path.join( + os.path.dirname(os.path.realpath(__file__)), "sg_logo_download.jpg" + ) result = self.sg.download_attachment(attach_id, file_path=file_path) self.assertEqual(result, file_path) # On windows read may not read to end of file unless opened 'rb' - fp = open(file_path, 'rb') + fp = open(file_path, "rb") attach_file = fp.read() fp.close() self.assertEqual(size, len(attach_file)) self.assertEqual(orig_file, attach_file) # test download with attachment hash - version = self.sg.find_one('Version', [['id', 'is', self.version['id']]], - ['sg_uploaded_movie']) + version = self.sg.find_one( + "Version", [["id", "is", self.version["id"]]], ["sg_uploaded_movie"] + ) # Look for the attachment we just uploaded, the attachments are not returned from latest # to earliest. - attachment = [v for k, v in version["sg_uploaded_movie"].items() if (k, v) == ("id", attach_id)] + attachment = [ + v + for k, v in version["sg_uploaded_movie"].items() + if (k, v) == ("id", attach_id) + ] self.assertEqual(len(attachment), 1) attachment = attachment[0] @@ -213,10 +213,9 @@ def test_upload_download(self): self.assertEqual(orig_file, attach_file) # test download with attachment hash (write to disk) - result = self.sg.download_attachment(attachment, - file_path=file_path) + result = self.sg.download_attachment(attachment, file_path=file_path) self.assertEqual(result, file_path) - fp = open(file_path, 'rb') + fp = open(file_path, "rb") attach_file = fp.read() fp.close() self.assertTrue(attach_file is not None) @@ -225,17 +224,23 @@ def test_upload_download(self): # test invalid requests INVALID_S3_URL = "https://sg-media-usor-01.s3.amazonaws.com/ada3de3ee3873875e1dd44f2eb0882c75ae36a4a/cd31346421dbeef781e0e480f259a3d36652d7f2/IMG_0465.MOV?AWSAccessKeyId=AKIAIQGOBSVN3FSQ5QFA&Expires=1371789959&Signature=SLbzv7DuVlZ8XAoOSQQAiGpF3u8%3D" # noqa - self.assertRaises(shotgun_api3.ShotgunFileDownloadError, - self.sg.download_attachment, - {"url": INVALID_S3_URL}) + self.assertRaises( + shotgun_api3.ShotgunFileDownloadError, + self.sg.download_attachment, + {"url": INVALID_S3_URL}, + ) INVALID_ATTACHMENT_ID = 99999999 - self.assertRaises(shotgun_api3.ShotgunFileDownloadError, - self.sg.download_attachment, - INVALID_ATTACHMENT_ID) - self.assertRaises(TypeError, self.sg.download_attachment, - "/path/to/some/file.jpg") - self.assertRaises(ValueError, self.sg.download_attachment, - {"id": 123, "type": "Shot"}) + self.assertRaises( + shotgun_api3.ShotgunFileDownloadError, + self.sg.download_attachment, + INVALID_ATTACHMENT_ID, + ) + self.assertRaises( + TypeError, self.sg.download_attachment, "/path/to/some/file.jpg" + ) + self.assertRaises( + ValueError, self.sg.download_attachment, {"id": 123, "type": "Shot"} + ) self.assertRaises(TypeError, self.sg.download_attachment) # test upload of non-ascii, unicode path @@ -251,10 +256,10 @@ def test_upload_download(self): # us up the way it used to. self.sg.upload( "Version", - self.version['id'], + self.version["id"], u_path, - 'sg_uploaded_movie', - tag_list="monkeys, everywhere, send, help" + "sg_uploaded_movie", + tag_list="monkeys, everywhere, send, help", ) # Also make sure that we can pass in a utf-8 encoded string path @@ -263,10 +268,10 @@ def test_upload_download(self): # situation as well as OS X and Linux. self.sg.upload( "Version", - self.version['id'], + self.version["id"], u_path.encode("utf-8"), - 'sg_uploaded_movie', - tag_list="monkeys, everywhere, send, help" + "sg_uploaded_movie", + tag_list="monkeys, everywhere, send, help", ) if six.PY2: # In Python2, make sure that non-utf-8 encoded paths raise when they @@ -280,26 +285,28 @@ def test_upload_download(self): file_path_s = os.path.join(this_dir, "./\xe3\x81\x94.shift-jis") file_path_u = file_path_s.decode("utf-8") - with open(file_path_u if sys.platform.startswith("win") else file_path_s, "w") as fh: + with open( + file_path_u if sys.platform.startswith("win") else file_path_s, "w" + ) as fh: fh.write("This is just a test file with some random data in it.") self.assertRaises( shotgun_api3.ShotgunError, self.sg.upload, "Version", - self.version['id'], + self.version["id"], file_path_u.encode("shift-jis"), - 'sg_uploaded_movie', - tag_list="monkeys, everywhere, send, help" + "sg_uploaded_movie", + tag_list="monkeys, everywhere, send, help", ) # But it should work in all cases if a unicode string is used. self.sg.upload( "Version", - self.version['id'], + self.version["id"], file_path_u, - 'sg_uploaded_movie', - tag_list="monkeys, everywhere, send, help" + "sg_uploaded_movie", + tag_list="monkeys, everywhere, send, help", ) # cleanup @@ -308,7 +315,7 @@ def test_upload_download(self): # cleanup os.remove(file_path) - @patch('shotgun_api3.Shotgun._send_form') + @patch("shotgun_api3.Shotgun._send_form") def test_upload_to_sg(self, mock_send_form): """ Upload an attachment tests for _upload_to_sg() @@ -324,24 +331,23 @@ def test_upload_to_sg(self, mock_send_form): ) upload_id = self.sg.upload( "Version", - self.version['id'], + self.version["id"], u_path, - 'attachments', - tag_list="monkeys, everywhere, send, help" + "attachments", + tag_list="monkeys, everywhere, send, help", ) mock_send_form_args, _ = mock_send_form.call_args display_name_to_send = mock_send_form_args[1].get("display_name", "") self.assertTrue(isinstance(upload_id, int)) self.assertFalse( - display_name_to_send.startswith("b'") and - display_name_to_send.endswith("'") + display_name_to_send.startswith("b'") and display_name_to_send.endswith("'") ) upload_id = self.sg.upload( "Version", - self.version['id'], + self.version["id"], u_path, - 'filmstrip_image', + "filmstrip_image", tag_list="monkeys, everywhere, send, help", ) self.assertTrue(isinstance(upload_id, int)) @@ -349,8 +355,7 @@ def test_upload_to_sg(self, mock_send_form): display_name_to_send = mock_send_form_args[1].get("display_name", "") self.assertTrue(isinstance(upload_id, int)) self.assertFalse( - display_name_to_send.startswith("b'") and - display_name_to_send.endswith("'") + display_name_to_send.startswith("b'") and display_name_to_send.endswith("'") ) mock_send_form.method.assert_called_once() @@ -359,23 +364,23 @@ def test_upload_to_sg(self, mock_send_form): shotgun_api3.ShotgunError, self.sg.upload, "Version", - self.version['id'], + self.version["id"], u_path, - 'attachments', - tag_list="monkeys, everywhere, send, help" + "attachments", + tag_list="monkeys, everywhere, send, help", ) self.sg.server_info["s3_direct_uploads_enabled"] = True def test_upload_thumbnail_in_create(self): """Upload a thumbnail via the create method""" this_dir, _ = os.path.split(__file__) - path = os.path.abspath(os.path.expanduser( - os.path.join(this_dir, "sg_logo.jpg"))) + path = os.path.abspath( + os.path.expanduser(os.path.join(this_dir, "sg_logo.jpg")) + ) # test thumbnail upload - data = {'image': path, 'code': 'Test Version', - 'project': self.project} - new_version = self.sg.create("Version", data, return_fields=['image']) + data = {"image": path, "code": "Test Version", "project": self.project} + new_version = self.sg.create("Version", data, return_fields=["image"]) new_version = self.find_one_await_thumbnail( "Version", [["id", "is", new_version["id"]]], @@ -384,104 +389,126 @@ def test_upload_thumbnail_in_create(self): self.assertTrue(new_version is not None) self.assertTrue(isinstance(new_version, dict)) - self.assertTrue(isinstance(new_version.get('id'), int)) - self.assertEqual(new_version.get('type'), 'Version') - self.assertEqual(new_version.get('project'), self.project) - self.assertTrue(new_version.get('image') is not None) + self.assertTrue(isinstance(new_version.get("id"), int)) + self.assertEqual(new_version.get("type"), "Version") + self.assertEqual(new_version.get("project"), self.project) + self.assertTrue(new_version.get("image") is not None) h = Http(".cache") - thumb_resp, content = h.request(new_version.get('image'), "GET") - self.assertIn(thumb_resp['status'], ['200', '304']) - self.assertIn(thumb_resp['content-type'], ['image/jpeg', 'image/png']) + thumb_resp, content = h.request(new_version.get("image"), "GET") + self.assertIn(thumb_resp["status"], ["200", "304"]) + self.assertIn(thumb_resp["content-type"], ["image/jpeg", "image/png"]) - self.sg.delete("Version", new_version['id']) + self.sg.delete("Version", new_version["id"]) # test filmstrip image upload - data = {'filmstrip_image': path, 'code': 'Test Version', - 'project': self.project} - new_version = self.sg.create("Version", data, return_fields=['filmstrip_image']) + data = { + "filmstrip_image": path, + "code": "Test Version", + "project": self.project, + } + new_version = self.sg.create("Version", data, return_fields=["filmstrip_image"]) self.assertTrue(new_version is not None) self.assertTrue(isinstance(new_version, dict)) - self.assertTrue(isinstance(new_version.get('id'), int)) - self.assertEqual(new_version.get('type'), 'Version') - self.assertEqual(new_version.get('project'), self.project) - self.assertTrue(new_version.get('filmstrip_image') is not None) + self.assertTrue(isinstance(new_version.get("id"), int)) + self.assertEqual(new_version.get("type"), "Version") + self.assertEqual(new_version.get("project"), self.project) + self.assertTrue(new_version.get("filmstrip_image") is not None) - url = new_version.get('filmstrip_image') - data = self.sg.download_attachment({'url': url}) + url = new_version.get("filmstrip_image") + data = self.sg.download_attachment({"url": url}) self.assertTrue(isinstance(data, six.binary_type)) - self.sg.delete("Version", new_version['id']) + self.sg.delete("Version", new_version["id"]) + # end test_upload_thumbnail_in_create def test_upload_thumbnail_for_version(self): """simple upload thumbnail for version test.""" this_dir, _ = os.path.split(__file__) - path = os.path.abspath(os.path.expanduser( - os.path.join(this_dir, "sg_logo.jpg"))) + path = os.path.abspath( + os.path.expanduser(os.path.join(this_dir, "sg_logo.jpg")) + ) # upload thumbnail - thumb_id = self.sg.upload_thumbnail("Version", self.version['id'], path) + thumb_id = self.sg.upload_thumbnail("Version", self.version["id"], path) self.assertTrue(isinstance(thumb_id, int)) # check result on version - version_with_thumbnail = self.sg.find_one('Version', [['id', 'is', self.version['id']]]) + version_with_thumbnail = self.sg.find_one( + "Version", [["id", "is", self.version["id"]]] + ) version_with_thumbnail = self.find_one_await_thumbnail( "Version", [["id", "is", self.version["id"]]] ) - self.assertEqual(version_with_thumbnail.get('type'), 'Version') - self.assertEqual(version_with_thumbnail.get('id'), self.version['id']) + self.assertEqual(version_with_thumbnail.get("type"), "Version") + self.assertEqual(version_with_thumbnail.get("id"), self.version["id"]) h = Http(".cache") - thumb_resp, content = h.request(version_with_thumbnail.get('image'), "GET") - self.assertIn(thumb_resp['status'], ['200', '304']) - self.assertIn(thumb_resp['content-type'], ['image/jpeg', 'image/png']) + thumb_resp, content = h.request(version_with_thumbnail.get("image"), "GET") + self.assertIn(thumb_resp["status"], ["200", "304"]) + self.assertIn(thumb_resp["content-type"], ["image/jpeg", "image/png"]) # clear thumbnail - response_clear_thumbnail = self.sg.update("Version", self.version['id'], {'image': None}) - expected_clear_thumbnail = {'id': self.version['id'], 'image': None, 'type': 'Version'} + response_clear_thumbnail = self.sg.update( + "Version", self.version["id"], {"image": None} + ) + expected_clear_thumbnail = { + "id": self.version["id"], + "image": None, + "type": "Version", + } self.assertEqual(expected_clear_thumbnail, response_clear_thumbnail) def test_upload_thumbnail_for_task(self): """simple upload thumbnail for task test.""" this_dir, _ = os.path.split(__file__) - path = os.path.abspath(os.path.expanduser( - os.path.join(this_dir, "sg_logo.jpg"))) + path = os.path.abspath( + os.path.expanduser(os.path.join(this_dir, "sg_logo.jpg")) + ) # upload thumbnail - thumb_id = self.sg.upload_thumbnail("Task", self.task['id'], path) + thumb_id = self.sg.upload_thumbnail("Task", self.task["id"], path) self.assertTrue(isinstance(thumb_id, int)) # check result on version - task_with_thumbnail = self.sg.find_one('Task', [['id', 'is', self.task['id']]]) + task_with_thumbnail = self.sg.find_one("Task", [["id", "is", self.task["id"]]]) task_with_thumbnail = self.find_one_await_thumbnail( "Task", [["id", "is", self.task["id"]]] ) - self.assertEqual(task_with_thumbnail.get('type'), 'Task') - self.assertEqual(task_with_thumbnail.get('id'), self.task['id']) + self.assertEqual(task_with_thumbnail.get("type"), "Task") + self.assertEqual(task_with_thumbnail.get("id"), self.task["id"]) h = Http(".cache") - thumb_resp, content = h.request(task_with_thumbnail.get('image'), "GET") - self.assertIn(thumb_resp['status'], ['200', '304']) - self.assertIn(thumb_resp['content-type'], ['image/jpeg', 'image/png']) + thumb_resp, content = h.request(task_with_thumbnail.get("image"), "GET") + self.assertIn(thumb_resp["status"], ["200", "304"]) + self.assertIn(thumb_resp["content-type"], ["image/jpeg", "image/png"]) # clear thumbnail - response_clear_thumbnail = self.sg.update("Version", self.version['id'], {'image': None}) - expected_clear_thumbnail = {'id': self.version['id'], 'image': None, 'type': 'Version'} + response_clear_thumbnail = self.sg.update( + "Version", self.version["id"], {"image": None} + ) + expected_clear_thumbnail = { + "id": self.version["id"], + "image": None, + "type": "Version", + } self.assertEqual(expected_clear_thumbnail, response_clear_thumbnail) def test_upload_thumbnail_with_upload_function(self): """Upload thumbnail via upload function test""" - path = os.path.abspath(os.path.expanduser(os.path.join(os.path.dirname(__file__), "sg_logo.jpg"))) + path = os.path.abspath( + os.path.expanduser(os.path.join(os.path.dirname(__file__), "sg_logo.jpg")) + ) # upload thumbnail - thumb_id = self.sg.upload("Task", self.task['id'], path, 'image') + thumb_id = self.sg.upload("Task", self.task["id"], path, "image") self.assertTrue(isinstance(thumb_id, int)) # upload filmstrip thumbnail - f_thumb_id = self.sg.upload("Task", self.task['id'], path, 'filmstrip_image') + f_thumb_id = self.sg.upload("Task", self.task["id"], path, "filmstrip_image") self.assertTrue(isinstance(f_thumb_id, int)) def test_requires_direct_s3_upload(self): @@ -494,19 +521,25 @@ def test_requires_direct_s3_upload(self): self.sg.server_info["s3_direct_uploads_enabled"] = None # Test s3_enabled_upload_types and s3_direct_uploads_enabled not set - self.assertFalse(self.sg._requires_direct_s3_upload("Version", "sg_uploaded_movie")) + self.assertFalse( + self.sg._requires_direct_s3_upload("Version", "sg_uploaded_movie") + ) self.sg.server_info["s3_enabled_upload_types"] = { "Version": ["sg_uploaded_movie"] } # Test direct_uploads_enabled not set - self.assertFalse(self.sg._requires_direct_s3_upload("Version", "sg_uploaded_movie")) + self.assertFalse( + self.sg._requires_direct_s3_upload("Version", "sg_uploaded_movie") + ) self.sg.server_info["s3_direct_uploads_enabled"] = True # Test regular path - self.assertTrue(self.sg._requires_direct_s3_upload("Version", "sg_uploaded_movie")) + self.assertTrue( + self.sg._requires_direct_s3_upload("Version", "sg_uploaded_movie") + ) self.assertFalse(self.sg._requires_direct_s3_upload("Version", "abc")) self.assertFalse(self.sg._requires_direct_s3_upload("Abc", "abc")) @@ -514,10 +547,12 @@ def test_requires_direct_s3_upload(self): self.sg.server_info["s3_enabled_upload_types"] = { "Version": ["sg_uploaded_movie", "test", "other"], "Test": ["*"], - "Asset": "*" + "Asset": "*", } - self.assertTrue(self.sg._requires_direct_s3_upload("Version", "sg_uploaded_movie")) + self.assertTrue( + self.sg._requires_direct_s3_upload("Version", "sg_uploaded_movie") + ) self.assertTrue(self.sg._requires_direct_s3_upload("Version", "test")) self.assertTrue(self.sg._requires_direct_s3_upload("Version", "other")) self.assertTrue(self.sg._requires_direct_s3_upload("Test", "abc")) @@ -525,22 +560,26 @@ def test_requires_direct_s3_upload(self): # Test default allowed upload type self.sg.server_info["s3_enabled_upload_types"] = None - self.assertTrue(self.sg._requires_direct_s3_upload("Version", "sg_uploaded_movie")) + self.assertTrue( + self.sg._requires_direct_s3_upload("Version", "sg_uploaded_movie") + ) self.assertFalse(self.sg._requires_direct_s3_upload("Version", "test")) # Test star entity_type self.sg.server_info["s3_enabled_upload_types"] = { "*": ["sg_uploaded_movie", "test"] } - self.assertTrue(self.sg._requires_direct_s3_upload("Something", "sg_uploaded_movie")) + self.assertTrue( + self.sg._requires_direct_s3_upload("Something", "sg_uploaded_movie") + ) self.assertTrue(self.sg._requires_direct_s3_upload("Version", "test")) self.assertFalse(self.sg._requires_direct_s3_upload("Version", "other")) # Test entity_type and field_name wildcard - self.sg.server_info["s3_enabled_upload_types"] = { - "*": "*" - } - self.assertTrue(self.sg._requires_direct_s3_upload("Something", "sg_uploaded_movie")) + self.sg.server_info["s3_enabled_upload_types"] = {"*": "*"} + self.assertTrue( + self.sg._requires_direct_s3_upload("Something", "sg_uploaded_movie") + ) self.assertTrue(self.sg._requires_direct_s3_upload("Version", "abc")) self.sg.server_info["s3_enabled_upload_types"] = upload_types @@ -548,10 +587,13 @@ def test_requires_direct_s3_upload(self): def test_linked_thumbnail_url(self): this_dir, _ = os.path.split(__file__) - path = os.path.abspath(os.path.expanduser( - os.path.join(this_dir, "sg_logo.jpg"))) + path = os.path.abspath( + os.path.expanduser(os.path.join(this_dir, "sg_logo.jpg")) + ) - thumb_id = self.sg.upload_thumbnail("Project", self.version['project']['id'], path) + thumb_id = self.sg.upload_thumbnail( + "Project", self.version["project"]["id"], path + ) response_version_with_project = self.find_one_await_thumbnail( "Version", @@ -562,23 +604,31 @@ def test_linked_thumbnail_url(self): if self.sg.server_caps.version and self.sg.server_caps.version >= (3, 3, 0): - self.assertEqual(response_version_with_project.get('type'), 'Version') - self.assertEqual(response_version_with_project.get('id'), self.version['id']) - self.assertEqual(response_version_with_project.get('code'), self.config.version_code) + self.assertEqual(response_version_with_project.get("type"), "Version") + self.assertEqual( + response_version_with_project.get("id"), self.version["id"] + ) + self.assertEqual( + response_version_with_project.get("code"), self.config.version_code + ) h = Http(".cache") - thumb_resp, content = h.request(response_version_with_project.get('project.Project.image'), "GET") - self.assertIn(thumb_resp['status'], ['200', '304']) - self.assertIn(thumb_resp['content-type'], ['image/jpeg', 'image/png']) + thumb_resp, content = h.request( + response_version_with_project.get("project.Project.image"), "GET" + ) + self.assertIn(thumb_resp["status"], ["200", "304"]) + self.assertIn(thumb_resp["content-type"], ["image/jpeg", "image/png"]) else: expected_version_with_project = { - 'code': self.config.version_code, - 'type': 'Version', - 'id': self.version['id'], - 'project.Project.image': thumb_id + "code": self.config.version_code, + "type": "Version", + "id": self.version["id"], + "project.Project.image": thumb_id, } - self.assertEqual(expected_version_with_project, response_version_with_project) + self.assertEqual( + expected_version_with_project, response_version_with_project + ) # For now skip tests that are erroneously failling on some sites to # allow CI to pass until the known issue causing this is resolved. @@ -601,49 +651,51 @@ def share_thumbnail_retry(*args, **kwargs): return thumbnail_id this_dir, _ = os.path.split(__file__) - path = os.path.abspath(os.path.expanduser(os.path.join(this_dir, "sg_logo.jpg"))) + path = os.path.abspath( + os.path.expanduser(os.path.join(this_dir, "sg_logo.jpg")) + ) # upload thumbnail to first entity and share it with the rest share_thumbnail_retry([self.version, self.shot], thumbnail_path=path) response_version_thumbnail = self.find_one_await_thumbnail( - 'Version', - [['id', 'is', self.version['id']]], - fields=['id', 'code', 'image'], + "Version", + [["id", "is", self.version["id"]]], + fields=["id", "code", "image"], ) response_shot_thumbnail = self.find_one_await_thumbnail( - 'Shot', - [['id', 'is', self.shot['id']]], - fields=['id', 'code', 'image'], + "Shot", + [["id", "is", self.shot["id"]]], + fields=["id", "code", "image"], ) - shot_url = urllib.parse.urlparse(response_shot_thumbnail.get('image')) - version_url = urllib.parse.urlparse(response_version_thumbnail.get('image')) + shot_url = urllib.parse.urlparse(response_shot_thumbnail.get("image")) + version_url = urllib.parse.urlparse(response_version_thumbnail.get("image")) shot_path = _get_path(shot_url) version_path = _get_path(version_url) self.assertEqual(shot_path, version_path) # share thumbnail from source entity with entities - self.sg.upload_thumbnail("Version", self.version['id'], path) + self.sg.upload_thumbnail("Version", self.version["id"], path) share_thumbnail_retry([self.asset, self.shot], source_entity=self.version) response_version_thumbnail = self.find_one_await_thumbnail( - 'Version', - [['id', 'is', self.version['id']]], - fields=['id', 'code', 'image'], + "Version", + [["id", "is", self.version["id"]]], + fields=["id", "code", "image"], ) response_shot_thumbnail = self.find_one_await_thumbnail( - 'Shot', - [['id', 'is', self.shot['id']]], - fields=['id', 'code', 'image'], + "Shot", + [["id", "is", self.shot["id"]]], + fields=["id", "code", "image"], ) response_asset_thumbnail = self.find_one_await_thumbnail( - 'Asset', - [['id', 'is', self.asset['id']]], - fields=['id', 'code', 'image'], + "Asset", + [["id", "is", self.asset["id"]]], + fields=["id", "code", "image"], ) - shot_url = urllib.parse.urlparse(response_shot_thumbnail.get('image')) - version_url = urllib.parse.urlparse(response_version_thumbnail.get('image')) - asset_url = urllib.parse.urlparse(response_asset_thumbnail.get('image')) + shot_url = urllib.parse.urlparse(response_shot_thumbnail.get("image")) + version_url = urllib.parse.urlparse(response_version_thumbnail.get("image")) + asset_url = urllib.parse.urlparse(response_asset_thumbnail.get("image")) shot_path = _get_path(shot_url) version_path = _get_path(version_url) @@ -653,32 +705,48 @@ def share_thumbnail_retry(*args, **kwargs): self.assertEqual(version_path, asset_path) # raise errors when missing required params or providing conflicting ones - self.assertRaises(shotgun_api3.ShotgunError, self.sg.share_thumbnail, - [self.shot, self.asset], path, self.version) - self.assertRaises(shotgun_api3.ShotgunError, self.sg.share_thumbnail, - [self.shot, self.asset]) + self.assertRaises( + shotgun_api3.ShotgunError, + self.sg.share_thumbnail, + [self.shot, self.asset], + path, + self.version, + ) + self.assertRaises( + shotgun_api3.ShotgunError, self.sg.share_thumbnail, [self.shot, self.asset] + ) - @patch('shotgun_api3.Shotgun._send_form') + @patch("shotgun_api3.Shotgun._send_form") def test_share_thumbnail_not_ready(self, mock_send_form): """throw an exception if trying to share a transient thumbnail""" mock_send_form.method.assert_called_once() - mock_send_form.return_value = ("2" - "\nsource_entity image is a transient thumbnail that cannot be shared. " - "Try again later, when the final thumbnail is available\n") + mock_send_form.return_value = ( + "2" + "\nsource_entity image is a transient thumbnail that cannot be shared. " + "Try again later, when the final thumbnail is available\n" + ) - self.assertRaises(shotgun_api3.ShotgunThumbnailNotReady, self.sg.share_thumbnail, - [self.version, self.shot], source_entity=self.asset) + self.assertRaises( + shotgun_api3.ShotgunThumbnailNotReady, + self.sg.share_thumbnail, + [self.version, self.shot], + source_entity=self.asset, + ) - @patch('shotgun_api3.Shotgun._send_form') + @patch("shotgun_api3.Shotgun._send_form") def test_share_thumbnail_returns_error(self, mock_send_form): """throw an exception if server returns an error code""" mock_send_form.method.assert_called_once() mock_send_form.return_value = "1\nerror message.\n" - self.assertRaises(shotgun_api3.ShotgunError, self.sg.share_thumbnail, - [self.version, self.shot], source_entity=self.asset) + self.assertRaises( + shotgun_api3.ShotgunError, + self.sg.share_thumbnail, + [self.version, self.shot], + source_entity=self.asset, + ) def test_deprecated_functions(self): """Deprecated functions raise errors""" @@ -687,35 +755,36 @@ def test_deprecated_functions(self): def test_simple_summary(self): """Test simple call to summarize""" - summaries = [{'field': 'id', 'type': 'count'}] - grouping = [{'direction': 'asc', 'field': 'id', 'type': 'exact'}] - filters = [['project', 'is', self.project]] - result = self.sg.summarize('Shot', - filters=filters, - summary_fields=summaries, - grouping=grouping) - assert(result['groups']) - assert(result['groups'][0]['group_name']) - assert(result['groups'][0]['group_value']) - assert(result['groups'][0]['summaries']) - assert(result['summaries']) + summaries = [{"field": "id", "type": "count"}] + grouping = [{"direction": "asc", "field": "id", "type": "exact"}] + filters = [["project", "is", self.project]] + result = self.sg.summarize( + "Shot", filters=filters, summary_fields=summaries, grouping=grouping + ) + assert result["groups"] + assert result["groups"][0]["group_name"] + assert result["groups"][0]["group_value"] + assert result["groups"][0]["summaries"] + assert result["summaries"] def test_summary_include_archived_projects(self): """Test summarize with archived project""" if self.sg.server_caps.version > (5, 3, 13): # archive project - self.sg.update('Project', self.project['id'], {'archived': True}) + self.sg.update("Project", self.project["id"], {"archived": True}) # Ticket #25082 ability to hide archived projects in summary - summaries = [{'field': 'id', 'type': 'count'}] - grouping = [{'direction': 'asc', 'field': 'id', 'type': 'exact'}] - filters = [['project', 'is', self.project]] - result = self.sg.summarize('Shot', - filters=filters, - summary_fields=summaries, - grouping=grouping, - include_archived_projects=False) - self.assertEqual(result['summaries']['id'], 0) - self.sg.update('Project', self.project['id'], {'archived': False}) + summaries = [{"field": "id", "type": "count"}] + grouping = [{"direction": "asc", "field": "id", "type": "exact"}] + filters = [["project", "is", self.project]] + result = self.sg.summarize( + "Shot", + filters=filters, + summary_fields=summaries, + grouping=grouping, + include_archived_projects=False, + ) + self.assertEqual(result["summaries"]["id"], 0) + self.sg.update("Project", self.project["id"], {"archived": False}) def test_summary_values(self): """Test summarize return data""" @@ -729,28 +798,28 @@ def test_summary_values(self): "code": "%s Shot 1" % shot_prefix, "sg_status_list": "ip", "sg_cut_duration": 100, - "project": self.project + "project": self.project, } shot_data_2 = { "code": "%s Shot 2" % shot_prefix, "sg_status_list": "ip", "sg_cut_duration": 100, - "project": self.project + "project": self.project, } shot_data_3 = { "code": "%s Shot 3" % shot_prefix, "sg_status_list": "fin", "sg_cut_duration": 100, - "project": self.project + "project": self.project, } shot_data_4 = { "code": "%s Shot 4" % shot_prefix, "sg_status_list": "wtg", "sg_cut_duration": 0, - "project": self.project + "project": self.project, } shots.append(self.sg.create("Shot", shot_data_1)) @@ -758,140 +827,167 @@ def test_summary_values(self): shots.append(self.sg.create("Shot", shot_data_3)) shots.append(self.sg.create("Shot", shot_data_4)) - summaries = [{'field': 'id', 'type': 'count'}, - {'field': 'sg_cut_duration', 'type': 'sum'}] - grouping = [{'direction': 'asc', - 'field': 'sg_status_list', - 'type': 'exact'}] - filters = [['project', 'is', self.project], - ['code', 'starts_with', shot_prefix]] - result = self.sg.summarize('Shot', - filters=filters, - summary_fields=summaries, - grouping=grouping) - count = {'id': 4, 'sg_cut_duration': 300} + summaries = [ + {"field": "id", "type": "count"}, + {"field": "sg_cut_duration", "type": "sum"}, + ] + grouping = [{"direction": "asc", "field": "sg_status_list", "type": "exact"}] + filters = [ + ["project", "is", self.project], + ["code", "starts_with", shot_prefix], + ] + result = self.sg.summarize( + "Shot", filters=filters, summary_fields=summaries, grouping=grouping + ) + count = {"id": 4, "sg_cut_duration": 300} groups = [ { - 'group_name': 'fin', - 'group_value': 'fin', - 'summaries': {'id': 1, 'sg_cut_duration': 100} + "group_name": "fin", + "group_value": "fin", + "summaries": {"id": 1, "sg_cut_duration": 100}, }, { - 'group_name': 'ip', - 'group_value': 'ip', - 'summaries': {'id': 2, 'sg_cut_duration': 200} + "group_name": "ip", + "group_value": "ip", + "summaries": {"id": 2, "sg_cut_duration": 200}, }, { - 'group_name': 'wtg', - 'group_value': 'wtg', - 'summaries': {'id': 1, 'sg_cut_duration': 0} - } + "group_name": "wtg", + "group_value": "wtg", + "summaries": {"id": 1, "sg_cut_duration": 0}, + }, ] # clean up batch_data = [] for s in shots: - batch_data.append({ - "request_type": "delete", - "entity_type": "Shot", - "entity_id": s["id"] - }) + batch_data.append( + {"request_type": "delete", "entity_type": "Shot", "entity_id": s["id"]} + ) self.sg.batch(batch_data) - self.assertEqual(result['summaries'], count) + self.assertEqual(result["summaries"], count) # Do not assume the order of the summarized results. self.assertEqual( - sorted( - result['groups'], - key=lambda x: x["group_name"] - ), - groups + sorted(result["groups"], key=lambda x: x["group_name"]), groups ) def test_ensure_ascii(self): - '''test_ensure_ascii tests ensure_unicode flag.''' - sg_ascii = shotgun_api3.Shotgun(self.config.server_url, - ensure_ascii=True, - **self.auth_args) + """test_ensure_ascii tests ensure_unicode flag.""" + sg_ascii = shotgun_api3.Shotgun( + self.config.server_url, ensure_ascii=True, **self.auth_args + ) - result = sg_ascii.find_one('Note', [['id', 'is', self.note['id']]], fields=['content']) + result = sg_ascii.find_one( + "Note", [["id", "is", self.note["id"]]], fields=["content"] + ) if six.PY2: # In Python3 there isn't a separate unicode type. self.assertFalse(_has_unicode(result)) def test_ensure_unicode(self): - '''test_ensure_unicode tests ensure_unicode flag.''' - sg_unicode = shotgun_api3.Shotgun(self.config.server_url, - ensure_ascii=False, - **self.auth_args) - result = sg_unicode.find_one('Note', [['id', 'is', self.note['id']]], fields=['content']) + """test_ensure_unicode tests ensure_unicode flag.""" + sg_unicode = shotgun_api3.Shotgun( + self.config.server_url, ensure_ascii=False, **self.auth_args + ) + result = sg_unicode.find_one( + "Note", [["id", "is", self.note["id"]]], fields=["content"] + ) self.assertTrue(_has_unicode(result)) def test_work_schedule(self): - '''test_work_schedule tests WorkDayRules api''' + """test_work_schedule tests WorkDayRules api""" self.maxDiff = None - start_date = '2012-01-01' + start_date = "2012-01-01" start_date_obj = datetime.datetime(2012, 1, 1) - end_date = '2012-01-07' + end_date = "2012-01-07" end_date_obj = datetime.datetime(2012, 1, 7) project = self.project # We're going to be comparing this value with the value returned from the server, so extract only the type, id # and name - user = {"type": self.human_user["type"], "id": self.human_user["id"], "name": self.human_user["name"]} + user = { + "type": self.human_user["type"], + "id": self.human_user["id"], + "name": self.human_user["name"], + } work_schedule = self.sg.work_schedule_read(start_date, end_date, project, user) # Test that the work_schedule_read api method is called with the 'start_date' and 'end_date' arguments # in the 'YYYY-MM-DD' string format. - self.assertRaises(shotgun_api3.ShotgunError, self.sg.work_schedule_read, - start_date_obj, end_date_obj, project, user) + self.assertRaises( + shotgun_api3.ShotgunError, + self.sg.work_schedule_read, + start_date_obj, + end_date_obj, + project, + user, + ) - resp = self.sg.work_schedule_update('2012-01-02', False, 'Studio Holiday') + resp = self.sg.work_schedule_update("2012-01-02", False, "Studio Holiday") expected = { - 'date': '2012-01-02', - 'description': 'Studio Holiday', - 'project': None, - 'user': None, - 'working': False + "date": "2012-01-02", + "description": "Studio Holiday", + "project": None, + "user": None, + "working": False, } self.assertEqual(expected, resp) resp = self.sg.work_schedule_read(start_date, end_date, project, user) - work_schedule['2012-01-02'] = {"reason": "STUDIO_EXCEPTION", "working": False, "description": "Studio Holiday"} + work_schedule["2012-01-02"] = { + "reason": "STUDIO_EXCEPTION", + "working": False, + "description": "Studio Holiday", + } self.assertEqual(work_schedule, resp) - resp = self.sg.work_schedule_update('2012-01-03', False, 'Project Holiday', project) + resp = self.sg.work_schedule_update( + "2012-01-03", False, "Project Holiday", project + ) expected = { - 'date': '2012-01-03', - 'description': 'Project Holiday', - 'project': project, - 'user': None, - 'working': False + "date": "2012-01-03", + "description": "Project Holiday", + "project": project, + "user": None, + "working": False, } self.assertEqual(expected, resp) resp = self.sg.work_schedule_read(start_date, end_date, project, user) - work_schedule['2012-01-03'] = { + work_schedule["2012-01-03"] = { "reason": "PROJECT_EXCEPTION", "working": False, - "description": "Project Holiday" + "description": "Project Holiday", } self.assertEqual(work_schedule, resp) jan4 = datetime.datetime(2012, 1, 4) - self.assertRaises(shotgun_api3.ShotgunError, self.sg.work_schedule_update, - jan4, False, 'Artist Holiday', user=user) + self.assertRaises( + shotgun_api3.ShotgunError, + self.sg.work_schedule_update, + jan4, + False, + "Artist Holiday", + user=user, + ) - resp = self.sg.work_schedule_update("2012-01-04", False, 'Artist Holiday', user=user) + resp = self.sg.work_schedule_update( + "2012-01-04", False, "Artist Holiday", user=user + ) expected = { - 'date': '2012-01-04', - 'description': 'Artist Holiday', - 'project': None, - 'user': user, - 'working': False + "date": "2012-01-04", + "description": "Artist Holiday", + "project": None, + "user": user, + "working": False, } self.assertEqual(expected, resp) resp = self.sg.work_schedule_read(start_date, end_date, project, user) - work_schedule['2012-01-04'] = {"reason": "USER_EXCEPTION", "working": False, "description": "Artist Holiday"} + work_schedule["2012-01-04"] = { + "reason": "USER_EXCEPTION", + "working": False, + "description": "Artist Holiday", + } self.assertEqual(work_schedule, resp) # test_preferences_read fails when preferences don't match the expected @@ -908,21 +1004,21 @@ def test_preferences_read(self): resp = self.sg.preferences_read() expected = { - 'date_component_order': 'month_day', - 'duration_units': 'days', - 'format_currency_fields_decimal_options': '$1,000.99', - 'format_currency_fields_display_dollar_sign': False, - 'format_currency_fields_negative_options': '- $1,000', - 'format_date_fields': '08/04/22 OR 04/08/22 (depending on the Month order preference)', - 'format_float_fields': '9,999.99', - 'format_float_fields_rounding': '9.999999', - 'format_footage_fields': '10-05', - 'format_number_fields': '1,000', - 'format_time_hour_fields': '12 hour', - 'hours_per_day': 8.0, - 'support_local_storage': True, - 'enable_rv_integration': True, - 'enable_shotgun_review_for_rv': False, + "date_component_order": "month_day", + "duration_units": "days", + "format_currency_fields_decimal_options": "$1,000.99", + "format_currency_fields_display_dollar_sign": False, + "format_currency_fields_negative_options": "- $1,000", + "format_date_fields": "08/04/22 OR 04/08/22 (depending on the Month order preference)", + "format_float_fields": "9,999.99", + "format_float_fields_rounding": "9.999999", + "format_footage_fields": "10-05", + "format_number_fields": "1,000", + "format_time_hour_fields": "12 hour", + "hours_per_day": 8.0, + "support_local_storage": True, + "enable_rv_integration": True, + "enable_shotgun_review_for_rv": False, } # Simply make sure viewmaster settings are there. These change frequently and we # don't want to have the test break because Viewmaster changed or because we didn't @@ -933,253 +1029,238 @@ def test_preferences_read(self): self.assertEqual(expected, resp) # all filtered - resp = self.sg.preferences_read(['date_component_order', 'support_local_storage']) + resp = self.sg.preferences_read( + ["date_component_order", "support_local_storage"] + ) - expected = { - 'date_component_order': 'month_day', - 'support_local_storage': True - } + expected = {"date_component_order": "month_day", "support_local_storage": True} self.assertEqual(expected, resp) # all filtered with invalid pref - resp = self.sg.preferences_read(['date_component_order', 'support_local_storage', 'email_notifications']) + resp = self.sg.preferences_read( + ["date_component_order", "support_local_storage", "email_notifications"] + ) - expected = { - 'date_component_order': 'month_day', - 'support_local_storage': True - } + expected = {"date_component_order": "month_day", "support_local_storage": True} self.assertEqual(expected, resp) class TestDataTypes(base.LiveTestBase): - '''Test fields representing the different data types mapped on the server side. + """Test fields representing the different data types mapped on the server side. - Untested data types: password, percent, pivot_column, serializable, image, currency - system_task_type, timecode, url, uuid, url_template - ''' + Untested data types: password, percent, pivot_column, serializable, image, currency + system_task_type, timecode, url, uuid, url_template + """ def setUp(self): super(TestDataTypes, self).setUp() def test_set_checkbox(self): - entity = 'HumanUser' - entity_id = self.human_user['id'] - field_name = 'email_notes' + entity = "HumanUser" + entity_id = self.human_user["id"] + field_name = "email_notes" pos_values = [False, True] - expected, actual = self.assert_set_field(entity, - entity_id, - field_name, - pos_values) + expected, actual = self.assert_set_field( + entity, entity_id, field_name, pos_values + ) self.assertEqual(expected, actual) def test_set_color(self): - entity = 'Task' - entity_id = self.task['id'] - field_name = 'color' - pos_values = ['pipeline_step', '222,0,0'] - expected, actual = self.assert_set_field(entity, - entity_id, - field_name, - pos_values) + entity = "Task" + entity_id = self.task["id"] + field_name = "color" + pos_values = ["pipeline_step", "222,0,0"] + expected, actual = self.assert_set_field( + entity, entity_id, field_name, pos_values + ) self.assertEqual(expected, actual) def test_set_date(self): - entity = 'Task' - entity_id = self.task['id'] - field_name = 'due_date' - pos_values = ['2008-05-08', '2011-05-05'] - expected, actual = self.assert_set_field(entity, - entity_id, - field_name, - pos_values) + entity = "Task" + entity_id = self.task["id"] + field_name = "due_date" + pos_values = ["2008-05-08", "2011-05-05"] + expected, actual = self.assert_set_field( + entity, entity_id, field_name, pos_values + ) self.assertEqual(expected, actual) def test_set_date_time(self): if self.config.jenkins: self.skipTest("Jenkins. locked_until not updating.") - entity = 'HumanUser' - entity_id = self.human_user['id'] - field_name = 'locked_until' + entity = "HumanUser" + entity_id = self.human_user["id"] + field_name = "locked_until" local = shotgun_api3.shotgun.SG_TIMEZONE.local dt_1 = datetime.datetime(2008, 10, 13, 23, 10, tzinfo=local) dt_2 = datetime.datetime(2009, 10, 13, 23, 10, tzinfo=local) pos_values = [dt_1, dt_2] - expected, actual = self.assert_set_field(entity, - entity_id, - field_name, - pos_values) + expected, actual = self.assert_set_field( + entity, entity_id, field_name, pos_values + ) self.assertEqual(expected, actual) def test_set_duration(self): - entity = 'Task' - entity_id = self.task['id'] - field_name = 'duration' + entity = "Task" + entity_id = self.task["id"] + field_name = "duration" pos_values = [2100, 1300] - expected, actual = self.assert_set_field(entity, - entity_id, - field_name, - pos_values) + expected, actual = self.assert_set_field( + entity, entity_id, field_name, pos_values + ) self.assertEqual(expected, actual) def test_set_entity(self): - entity = 'Task' - entity_id = self.task['id'] - field_name = 'entity' + entity = "Task" + entity_id = self.task["id"] + field_name = "entity" pos_values = [self.asset, self.shot] - expected, actual = self.assert_set_field(entity, - entity_id, - field_name, - pos_values) - self.assertEqual(expected['id'], actual['id']) + expected, actual = self.assert_set_field( + entity, entity_id, field_name, pos_values + ) + self.assertEqual(expected["id"], actual["id"]) def test_set_float(self): - entity = 'Version' - entity_id = self.version['id'] - field_name = 'sg_movie_aspect_ratio' + entity = "Version" + entity_id = self.version["id"] + field_name = "sg_movie_aspect_ratio" pos_values = [2.0, 3.0] - expected, actual = self.assert_set_field(entity, - entity_id, - field_name, - pos_values) + expected, actual = self.assert_set_field( + entity, entity_id, field_name, pos_values + ) self.assertEqual(expected, actual) def test_set_list(self): - entity = 'Note' - entity_id = self.note['id'] - field_name = 'sg_note_type' - pos_values = ['Internal', 'Client'] - expected, actual = self.assert_set_field(entity, - entity_id, - field_name, - pos_values) + entity = "Note" + entity_id = self.note["id"] + field_name = "sg_note_type" + pos_values = ["Internal", "Client"] + expected, actual = self.assert_set_field( + entity, entity_id, field_name, pos_values + ) self.assertEqual(expected, actual) def test_set_multi_entity(self): - sg = shotgun_api3.Shotgun(self.config.server_url, - **self.auth_args) - keys = ['project', 'user', 'code'] - data = {'project': self.project, - 'user': self.human_user, - 'code': 'Alpha'} - version_1 = base._find_or_create_entity(sg, 'Version', data, keys) - data = {'project': self.project, - 'user': self.human_user, - 'code': 'Beta'} - version_2 = base._find_or_create_entity(sg, 'Version', data, keys) - - entity = 'Playlist' - entity_id = self.playlist['id'] - field_name = 'versions' + sg = shotgun_api3.Shotgun(self.config.server_url, **self.auth_args) + keys = ["project", "user", "code"] + data = {"project": self.project, "user": self.human_user, "code": "Alpha"} + version_1 = base._find_or_create_entity(sg, "Version", data, keys) + data = {"project": self.project, "user": self.human_user, "code": "Beta"} + version_2 = base._find_or_create_entity(sg, "Version", data, keys) + + entity = "Playlist" + entity_id = self.playlist["id"] + field_name = "versions" # Default set behaviour pos_values = [[version_1, version_2]] - expected, actual = self.assert_set_field(entity, entity_id, field_name, pos_values) + expected, actual = self.assert_set_field( + entity, entity_id, field_name, pos_values + ) self.assertEqual(len(expected), len(actual)) self.assertEqual( - sorted([x['id'] for x in expected]), - sorted([x['id'] for x in actual]) + sorted([x["id"] for x in expected]), sorted([x["id"] for x in actual]) ) # Multi-entity remove mode pos_values = [[version_1]] - expected, actual = self.assert_set_field(entity, entity_id, field_name, pos_values, - multi_entity_update_mode='remove') + expected, actual = self.assert_set_field( + entity, entity_id, field_name, pos_values, multi_entity_update_mode="remove" + ) self.assertEqual(1, len(actual)) self.assertEqual(len(expected), len(actual)) - self.assertNotEqual(expected[0]['id'], actual[0]['id']) - self.assertEqual(version_2['id'], actual[0]['id']) + self.assertNotEqual(expected[0]["id"], actual[0]["id"]) + self.assertEqual(version_2["id"], actual[0]["id"]) # Multi-entity add mode pos_values = [[version_1]] - expected, actual = self.assert_set_field(entity, entity_id, field_name, pos_values, - multi_entity_update_mode='add') + expected, actual = self.assert_set_field( + entity, entity_id, field_name, pos_values, multi_entity_update_mode="add" + ) self.assertEqual(2, len(actual)) - self.assertTrue(version_1['id'] in [x['id'] for x in actual]) + self.assertTrue(version_1["id"] in [x["id"] for x in actual]) # Multi-entity set mode pos_values = [[version_1, version_2]] - expected, actual = self.assert_set_field(entity, entity_id, field_name, pos_values, - multi_entity_update_mode='set') + expected, actual = self.assert_set_field( + entity, entity_id, field_name, pos_values, multi_entity_update_mode="set" + ) self.assertEqual(len(expected), len(actual)) self.assertEqual( - sorted([x['id'] for x in expected]), - sorted([x['id'] for x in actual]) + sorted([x["id"] for x in expected]), sorted([x["id"] for x in actual]) ) def test_set_number(self): - entity = 'Shot' - entity_id = self.shot['id'] - field_name = 'head_in' + entity = "Shot" + entity_id = self.shot["id"] + field_name = "head_in" pos_values = [2300, 1300] - expected, actual = self.assert_set_field(entity, - entity_id, - field_name, - pos_values) + expected, actual = self.assert_set_field( + entity, entity_id, field_name, pos_values + ) self.assertEqual(expected, actual) def test_set_status_list(self): - entity = 'Task' - entity_id = self.task['id'] - field_name = 'sg_status_list' - pos_values = ['wtg', 'fin'] - expected, actual = self.assert_set_field(entity, - entity_id, - field_name, - pos_values) + entity = "Task" + entity_id = self.task["id"] + field_name = "sg_status_list" + pos_values = ["wtg", "fin"] + expected, actual = self.assert_set_field( + entity, entity_id, field_name, pos_values + ) self.assertEqual(expected, actual) def test_set_tag_list(self): - entity = 'Task' - entity_id = self.task['id'] - field_name = 'tag_list' - pos_values = [['a', 'b'], ['c']] - expected, actual = self.assert_set_field(entity, - entity_id, - field_name, - pos_values) + entity = "Task" + entity_id = self.task["id"] + field_name = "tag_list" + pos_values = [["a", "b"], ["c"]] + expected, actual = self.assert_set_field( + entity, entity_id, field_name, pos_values + ) self.assertEqual(expected, actual) def test_set_text(self): - entity = 'Note' - entity_id = self.note['id'] - field_name = 'content' - pos_values = ['this content', 'that content'] - expected, actual = self.assert_set_field(entity, - entity_id, - field_name, - pos_values) + entity = "Note" + entity_id = self.note["id"] + field_name = "content" + pos_values = ["this content", "that content"] + expected, actual = self.assert_set_field( + entity, entity_id, field_name, pos_values + ) self.assertEqual(expected, actual) def test_set_text_html_entity(self): - entity = 'Note' - entity_id = self.note['id'] - field_name = 'content' - pos_values = ['<', '<'] - expected, actual = self.assert_set_field(entity, - entity_id, - field_name, - pos_values) + entity = "Note" + entity_id = self.note["id"] + field_name = "content" + pos_values = ["<", "<"] + expected, actual = self.assert_set_field( + entity, entity_id, field_name, pos_values + ) self.assertEqual(expected, actual) - def assert_set_field(self, entity, entity_id, field_name, pos_values, multi_entity_update_mode=None): - query_result = self.sg.find_one(entity, - [['id', 'is', entity_id]], - [field_name]) + def assert_set_field( + self, entity, entity_id, field_name, pos_values, multi_entity_update_mode=None + ): + query_result = self.sg.find_one(entity, [["id", "is", entity_id]], [field_name]) initial_value = query_result[field_name] new_value = (initial_value == pos_values[0] and pos_values[1]) or pos_values[0] if multi_entity_update_mode: - self.sg.update(entity, entity_id, {field_name: new_value}, - multi_entity_update_modes={field_name: multi_entity_update_mode}) + self.sg.update( + entity, + entity_id, + {field_name: new_value}, + multi_entity_update_modes={field_name: multi_entity_update_mode}, + ) else: self.sg.update(entity, entity_id, {field_name: new_value}) - new_values = self.sg.find_one(entity, - [['id', 'is', entity_id]], - [field_name]) + new_values = self.sg.find_one(entity, [["id", "is", entity_id]], [field_name]) return new_value, new_values[field_name] class TestUtc(base.LiveTestBase): - '''Test utc options''' + """Test utc options""" def setUp(self): super(TestUtc, self).setUp() @@ -1192,29 +1273,33 @@ def setUp(self): def test_convert_to_utc(self): if self.config.jenkins: self.skipTest("Jenkins. locked_until not updating.") - sg_utc = shotgun_api3.Shotgun(self.config.server_url, - http_proxy=self.config.http_proxy, - convert_datetimes_to_utc=True, - **self.auth_args) + sg_utc = shotgun_api3.Shotgun( + self.config.server_url, + http_proxy=self.config.http_proxy, + convert_datetimes_to_utc=True, + **self.auth_args, + ) self._assert_expected(sg_utc, self.datetime_none, self.datetime_local) self._assert_expected(sg_utc, self.datetime_local, self.datetime_local) def test_no_convert_to_utc(self): if self.config.jenkins: self.skipTest("Jenkins. locked_until not updating.") - sg_no_utc = shotgun_api3.Shotgun(self.config.server_url, - http_proxy=self.config.http_proxy, - convert_datetimes_to_utc=False, - **self.auth_args) + sg_no_utc = shotgun_api3.Shotgun( + self.config.server_url, + http_proxy=self.config.http_proxy, + convert_datetimes_to_utc=False, + **self.auth_args, + ) self._assert_expected(sg_no_utc, self.datetime_none, self.datetime_none) self._assert_expected(sg_no_utc, self.datetime_utc, self.datetime_none) def _assert_expected(self, sg, date_time, expected): - entity_name = 'HumanUser' - entity_id = self.human_user['id'] - field_name = 'locked_until' + entity_name = "HumanUser" + entity_id = self.human_user["id"] + field_name = "locked_until" sg.update(entity_name, entity_id, {field_name: date_time}) - result = sg.find_one(entity_name, [['id', 'is', entity_id]], [field_name]) + result = sg.find_one(entity_name, [["id", "is", entity_id]], [field_name]) self.assertEqual(result[field_name], expected) @@ -1223,31 +1308,33 @@ def setUp(self): super(TestFind, self).setUp() # We will need the created_at field for the shot fields = list(self.shot.keys())[:] - fields.append('created_at') - self.shot = self.sg.find_one('Shot', [['id', 'is', self.shot['id']]], fields) + fields.append("created_at") + self.shot = self.sg.find_one("Shot", [["id", "is", self.shot["id"]]], fields) # We will need the uuid field for our LocalStorage fields = list(self.local_storage.keys())[:] - fields.append('uuid') - self.local_storage = self.sg.find_one('LocalStorage', [['id', 'is', self.local_storage['id']]], fields) + fields.append("uuid") + self.local_storage = self.sg.find_one( + "LocalStorage", [["id", "is", self.local_storage["id"]]], fields + ) def test_find(self): """Called find, find_one for known entities""" filters = [] - filters.append(['project', 'is', self.project]) - filters.append(['id', 'is', self.version['id']]) + filters.append(["project", "is", self.project]) + filters.append(["id", "is", self.version["id"]]) - fields = ['id'] + fields = ["id"] versions = self.sg.find("Version", filters, fields=fields) self.assertTrue(isinstance(versions, list)) version = versions[0] self.assertEqual("Version", version["type"]) - self.assertEqual(self.version['id'], version["id"]) + self.assertEqual(self.version["id"], version["id"]) version = self.sg.find_one("Version", filters, fields=fields) self.assertEqual("Version", version["type"]) - self.assertEqual(self.version['id'], version["id"]) + self.assertEqual(self.version["id"], version["id"]) def _id_in_result(self, entity_type, filters, expected_id): """ @@ -1255,126 +1342,138 @@ def _id_in_result(self, entity_type, filters, expected_id): for particular filters. """ results = self.sg.find(entity_type, filters) - return any(result['id'] == expected_id for result in results) + return any(result["id"] == expected_id for result in results) # TODO test all applicable data types for 'in' - # 'currency' => [BigDecimal, Float, NilClass], - # 'image' => [Hash, NilClass], - # 'percent' => [Bignum, Fixnum, NilClass], - # 'serializable' => [Hash, Array, NilClass], - # 'system_task_type' => [String, NilClass], - # 'timecode' => [Bignum, Fixnum, NilClass], - # 'footage' => [Bignum, Fixnum, NilClass, String, Float, BigDecimal], - # 'url' => [Hash, NilClass], + # 'currency' => [BigDecimal, Float, NilClass], + # 'image' => [Hash, NilClass], + # 'percent' => [Bignum, Fixnum, NilClass], + # 'serializable' => [Hash, Array, NilClass], + # 'system_task_type' => [String, NilClass], + # 'timecode' => [Bignum, Fixnum, NilClass], + # 'footage' => [Bignum, Fixnum, NilClass, String, Float, BigDecimal], + # 'url' => [Hash, NilClass], - # 'uuid' => [String], + # 'uuid' => [String], def test_in_relation_comma_id(self): """ Test that 'in' relation using commas (old format) works with ids. """ - filters = [['id', 'in', self.project['id'], 99999]] - result = self._id_in_result('Project', filters, self.project['id']) + filters = [["id", "in", self.project["id"], 99999]] + result = self._id_in_result("Project", filters, self.project["id"]) self.assertTrue(result) def test_in_relation_list_id(self): """ Test that 'in' relation using list (new format) works with ids. """ - filters = [['id', 'in', [self.project['id'], 99999]]] - result = self._id_in_result('Project', filters, self.project['id']) + filters = [["id", "in", [self.project["id"], 99999]]] + result = self._id_in_result("Project", filters, self.project["id"]) self.assertTrue(result) def test_not_in_relation_id(self): """ Test that 'not_in' relation using commas (old format) works with ids. """ - filters = [['id', 'not_in', self.project['id'], 99999]] - result = self._id_in_result('Project', filters, self.project['id']) + filters = [["id", "not_in", self.project["id"], 99999]] + result = self._id_in_result("Project", filters, self.project["id"]) self.assertFalse(result) def test_in_relation_comma_text(self): """ Test that 'in' relation using commas (old format) works with text fields. """ - filters = [['name', 'in', self.project['name'], 'fake project name']] - result = self._id_in_result('Project', filters, self.project['id']) + filters = [["name", "in", self.project["name"], "fake project name"]] + result = self._id_in_result("Project", filters, self.project["id"]) self.assertTrue(result) def test_in_relation_list_text(self): """ Test that 'in' relation using list (new format) works with text fields. """ - filters = [['name', 'in', [self.project['name'], 'fake project name']]] - result = self._id_in_result('Project', filters, self.project['id']) + filters = [["name", "in", [self.project["name"], "fake project name"]]] + result = self._id_in_result("Project", filters, self.project["id"]) self.assertTrue(result) def test_not_in_relation_text(self): """ Test that 'not_in' relation using commas (old format) works with ids. """ - filters = [['name', 'not_in', [self.project['name'], 'fake project name']]] - result = self._id_in_result('Project', filters, self.project['id']) + filters = [["name", "not_in", [self.project["name"], "fake project name"]]] + result = self._id_in_result("Project", filters, self.project["id"]) self.assertFalse(result) def test_in_relation_comma_color(self): """ Test that 'in' relation using commas (old format) works with color fields. """ - filters = [['color', 'in', self.task['color'], 'Green'], - ['project', 'is', self.project]] + filters = [ + ["color", "in", self.task["color"], "Green"], + ["project", "is", self.project], + ] - result = self._id_in_result('Task', filters, self.task['id']) + result = self._id_in_result("Task", filters, self.task["id"]) self.assertTrue(result) def test_in_relation_list_color(self): """ Test that 'in' relation using list (new format) works with color fields. """ - filters = [['color', 'in', [self.task['color'], 'Green']], - ['project', 'is', self.project]] + filters = [ + ["color", "in", [self.task["color"], "Green"]], + ["project", "is", self.project], + ] - result = self._id_in_result('Task', filters, self.task['id']) + result = self._id_in_result("Task", filters, self.task["id"]) self.assertTrue(result) def test_not_in_relation_color(self): """ Test that 'not_in' relation using commas (old format) works with color fields. """ - filters = [['color', 'not_in', [self.task['color'], 'Green']], - ['project', 'is', self.project]] + filters = [ + ["color", "not_in", [self.task["color"], "Green"]], + ["project", "is", self.project], + ] - result = self._id_in_result('Task', filters, self.task['id']) + result = self._id_in_result("Task", filters, self.task["id"]) self.assertFalse(result) def test_in_relation_comma_date(self): """ Test that 'in' relation using commas (old format) works with date fields. """ - filters = [['due_date', 'in', self.task['due_date'], '2012-11-25'], - ['project', 'is', self.project]] + filters = [ + ["due_date", "in", self.task["due_date"], "2012-11-25"], + ["project", "is", self.project], + ] - result = self._id_in_result('Task', filters, self.task['id']) + result = self._id_in_result("Task", filters, self.task["id"]) self.assertTrue(result) def test_in_relation_list_date(self): """ Test that 'in' relation using list (new format) works with date fields. """ - filters = [['due_date', 'in', [self.task['due_date'], '2012-11-25']], - ['project', 'is', self.project]] + filters = [ + ["due_date", "in", [self.task["due_date"], "2012-11-25"]], + ["project", "is", self.project], + ] - result = self._id_in_result('Task', filters, self.task['id']) + result = self._id_in_result("Task", filters, self.task["id"]) self.assertTrue(result) def test_not_in_relation_date(self): """ Test that 'not_in' relation using commas (old format) works with date fields. """ - filters = [['due_date', 'not_in', [self.task['due_date'], '2012-11-25']], - ['project', 'is', self.project]] + filters = [ + ["due_date", "not_in", [self.task["due_date"], "2012-11-25"]], + ["project", "is", self.project], + ] - result = self._id_in_result('Task', filters, self.task['id']) + result = self._id_in_result("Task", filters, self.task["id"]) self.assertFalse(result) # TODO add datetime test for in and not_in @@ -1385,12 +1484,16 @@ def test_in_relation_comma_duration(self): """ # we need to get the duration value new_task_keys = list(self.task.keys())[:] - new_task_keys.append('duration') - self.task = self.sg.find_one('Task', [['id', 'is', self.task['id']]], new_task_keys) - filters = [['duration', 'in', self.task['duration']], - ['project', 'is', self.project]] + new_task_keys.append("duration") + self.task = self.sg.find_one( + "Task", [["id", "is", self.task["id"]]], new_task_keys + ) + filters = [ + ["duration", "in", self.task["duration"]], + ["project", "is", self.project], + ] - result = self._id_in_result('Task', filters, self.task['id']) + result = self._id_in_result("Task", filters, self.task["id"]) self.assertTrue(result) def test_in_relation_list_duration(self): @@ -1399,12 +1502,22 @@ def test_in_relation_list_duration(self): """ # we need to get the duration value new_task_keys = list(self.task.keys())[:] - new_task_keys.append('duration') - self.task = self.sg.find_one('Task', [['id', 'is', self.task['id']]], new_task_keys) - filters = [['duration', 'in', [self.task['duration'], ]], - ['project', 'is', self.project]] + new_task_keys.append("duration") + self.task = self.sg.find_one( + "Task", [["id", "is", self.task["id"]]], new_task_keys + ) + filters = [ + [ + "duration", + "in", + [ + self.task["duration"], + ], + ], + ["project", "is", self.project], + ] - result = self._id_in_result('Task', filters, self.task['id']) + result = self._id_in_result("Task", filters, self.task["id"]) self.assertTrue(result) def test_not_in_relation_duration(self): @@ -1413,339 +1526,473 @@ def test_not_in_relation_duration(self): """ # we need to get the duration value new_task_keys = list(self.task.keys())[:] - new_task_keys.append('duration') - self.task = self.sg.find_one('Task', [['id', 'is', self.task['id']]], new_task_keys) + new_task_keys.append("duration") + self.task = self.sg.find_one( + "Task", [["id", "is", self.task["id"]]], new_task_keys + ) - filters = [['duration', 'not_in', [self.task['duration'], ]], - ['project', 'is', self.project]] + filters = [ + [ + "duration", + "not_in", + [ + self.task["duration"], + ], + ], + ["project", "is", self.project], + ] - result = self._id_in_result('Task', filters, self.task['id']) + result = self._id_in_result("Task", filters, self.task["id"]) self.assertFalse(result) def test_in_relation_comma_entity(self): """ Test that 'in' relation using commas (old format) works with entity fields. """ - filters = [['entity', 'in', self.task['entity'], self.asset], - ['project', 'is', self.project]] + filters = [ + ["entity", "in", self.task["entity"], self.asset], + ["project", "is", self.project], + ] - result = self._id_in_result('Task', filters, self.task['id']) + result = self._id_in_result("Task", filters, self.task["id"]) self.assertTrue(result) def test_in_relation_list_entity(self): """ Test that 'in' relation using list (new format) works with entity fields. """ - filters = [['entity', 'in', [self.task['entity'], self.asset]], - ['project', 'is', self.project]] + filters = [ + ["entity", "in", [self.task["entity"], self.asset]], + ["project", "is", self.project], + ] - result = self._id_in_result('Task', filters, self.task['id']) + result = self._id_in_result("Task", filters, self.task["id"]) self.assertTrue(result) def test_not_in_relation_entity(self): """ Test that 'not_in' relation using commas (old format) works with entity fields. """ - filters = [['entity', 'not_in', [self.task['entity'], self.asset]], - ['project', 'is', self.project]] + filters = [ + ["entity", "not_in", [self.task["entity"], self.asset]], + ["project", "is", self.project], + ] - result = self._id_in_result('Task', filters, self.task['id']) + result = self._id_in_result("Task", filters, self.task["id"]) self.assertFalse(result) def test_in_relation_comma_entity_type(self): """ Test that 'in' relation using commas (old format) works with entity_type fields. """ - filters = [['entity_type', 'in', self.step['entity_type'], 'something else']] + filters = [["entity_type", "in", self.step["entity_type"], "something else"]] - result = self._id_in_result('Step', filters, self.step['id']) + result = self._id_in_result("Step", filters, self.step["id"]) self.assertTrue(result) def test_in_relation_list_entity_type(self): """ Test that 'in' relation using list (new format) works with entity_type fields. """ - filters = [['entity_type', 'in', [self.step['entity_type'], 'something else']]] + filters = [["entity_type", "in", [self.step["entity_type"], "something else"]]] - result = self._id_in_result('Step', filters, self.step['id']) + result = self._id_in_result("Step", filters, self.step["id"]) self.assertTrue(result) def test_not_in_relation_entity_type(self): """ Test that 'not_in' relation using commas (old format) works with entity_type fields. """ - filters = [['entity_type', 'not_in', [self.step['entity_type'], 'something else']]] + filters = [ + ["entity_type", "not_in", [self.step["entity_type"], "something else"]] + ] - result = self._id_in_result('Step', filters, self.step['id']) + result = self._id_in_result("Step", filters, self.step["id"]) self.assertFalse(result) def test_in_relation_comma_float(self): """ Test that 'in' relation using commas (old format) works with float fields. """ - filters = [['sg_frames_aspect_ratio', 'in', self.version['sg_frames_aspect_ratio'], 44.0], - ['project', 'is', self.project]] + filters = [ + [ + "sg_frames_aspect_ratio", + "in", + self.version["sg_frames_aspect_ratio"], + 44.0, + ], + ["project", "is", self.project], + ] - result = self._id_in_result('Version', filters, self.version['id']) + result = self._id_in_result("Version", filters, self.version["id"]) self.assertTrue(result) def test_in_relation_list_float(self): """ Test that 'in' relation using list (new format) works with float fields. """ - filters = [['sg_frames_aspect_ratio', 'in', [self.version['sg_frames_aspect_ratio'], 30.0]], - ['project', 'is', self.project]] + filters = [ + [ + "sg_frames_aspect_ratio", + "in", + [self.version["sg_frames_aspect_ratio"], 30.0], + ], + ["project", "is", self.project], + ] - result = self._id_in_result('Version', filters, self.version['id']) + result = self._id_in_result("Version", filters, self.version["id"]) self.assertTrue(result) def test_not_in_relation_float(self): """ Test that 'not_in' relation using commas (old format) works with float fields. """ - filters = [['sg_frames_aspect_ratio', 'not_in', [self.version['sg_frames_aspect_ratio'], 4.4]], - ['project', 'is', self.project]] + filters = [ + [ + "sg_frames_aspect_ratio", + "not_in", + [self.version["sg_frames_aspect_ratio"], 4.4], + ], + ["project", "is", self.project], + ] - result = self._id_in_result('Version', filters, self.version['id']) + result = self._id_in_result("Version", filters, self.version["id"]) self.assertFalse(result) def test_in_relation_comma_list(self): """ Test that 'in' relation using commas (old format) works with list fields. """ - filters = [['frame_count', 'in', self.version['frame_count'], 33], - ['project', 'is', self.project]] + filters = [ + ["frame_count", "in", self.version["frame_count"], 33], + ["project", "is", self.project], + ] - result = self._id_in_result('Version', filters, self.version['id']) + result = self._id_in_result("Version", filters, self.version["id"]) self.assertTrue(result) def test_in_relation_list_list(self): """ Test that 'in' relation using list (new format) works with list fields. """ - filters = [['frame_count', 'in', [self.version['frame_count'], 33]], - ['project', 'is', self.project]] + filters = [ + ["frame_count", "in", [self.version["frame_count"], 33]], + ["project", "is", self.project], + ] - result = self._id_in_result('Version', filters, self.version['id']) + result = self._id_in_result("Version", filters, self.version["id"]) self.assertTrue(result) def test_not_in_relation_list(self): """ Test that 'not_in' relation using commas (old format) works with list fields. """ - filters = [['frame_count', 'not_in', [self.version['frame_count'], 33]], - ['project', 'is', self.project]] + filters = [ + ["frame_count", "not_in", [self.version["frame_count"], 33]], + ["project", "is", self.project], + ] - result = self._id_in_result('Version', filters, self.version['id']) + result = self._id_in_result("Version", filters, self.version["id"]) self.assertFalse(result) def test_in_relation_comma_multi_entity(self): """ Test that 'in' relation using commas (old format) works with multi_entity fields. """ - filters = [['task_assignees', 'in', self.human_user, ], - ['project', 'is', self.project]] + filters = [ + [ + "task_assignees", + "in", + self.human_user, + ], + ["project", "is", self.project], + ] - result = self._id_in_result('Task', filters, self.task['id']) + result = self._id_in_result("Task", filters, self.task["id"]) self.assertTrue(result) def test_in_relation_list_multi_entity(self): """ Test that 'in' relation using list (new format) works with multi_entity fields. """ - filters = [['task_assignees', 'in', [self.human_user, ]], - ['project', 'is', self.project]] + filters = [ + [ + "task_assignees", + "in", + [ + self.human_user, + ], + ], + ["project", "is", self.project], + ] - result = self._id_in_result('Task', filters, self.task['id']) + result = self._id_in_result("Task", filters, self.task["id"]) self.assertTrue(result) def test_not_in_relation_multi_entity(self): """ Test that 'not_in' relation using commas (old format) works with multi_entity fields. """ - filters = [['task_assignees', 'not_in', [self.human_user, ]], - ['project', 'is', self.project]] + filters = [ + [ + "task_assignees", + "not_in", + [ + self.human_user, + ], + ], + ["project", "is", self.project], + ] - result = self._id_in_result('Task', filters, self.task['id']) + result = self._id_in_result("Task", filters, self.task["id"]) self.assertFalse(result) def test_in_relation_comma_number(self): """ Test that 'in' relation using commas (old format) works with number fields. """ - filters = [['frame_count', 'in', self.version['frame_count'], 1], - ['project', 'is', self.project]] + filters = [ + ["frame_count", "in", self.version["frame_count"], 1], + ["project", "is", self.project], + ] - result = self._id_in_result('Version', filters, self.version['id']) + result = self._id_in_result("Version", filters, self.version["id"]) self.assertTrue(result) def test_in_relation_list_number(self): """ Test that 'in' relation using list (new format) works with number fields. """ - filters = [['frame_count', 'in', [self.version['frame_count'], 1]], - ['project', 'is', self.project]] + filters = [ + ["frame_count", "in", [self.version["frame_count"], 1]], + ["project", "is", self.project], + ] - result = self._id_in_result('Version', filters, self.version['id']) + result = self._id_in_result("Version", filters, self.version["id"]) self.assertTrue(result) def test_not_in_relation_number(self): """ Test that 'not_in' relation using commas (old format) works with number fields. """ - filters = [['frame_count', 'not_in', [self.version['frame_count'], 1]], - ['project', 'is', self.project]] + filters = [ + ["frame_count", "not_in", [self.version["frame_count"], 1]], + ["project", "is", self.project], + ] - result = self._id_in_result('Version', filters, self.version['id']) + result = self._id_in_result("Version", filters, self.version["id"]) self.assertFalse(result) def test_in_relation_comma_status_list(self): """ Test that 'in' relation using commas (old format) works with status_list fields. """ - filters = [['sg_status_list', 'in', self.task['sg_status_list'], 'fin'], - ['project', 'is', self.project]] + filters = [ + ["sg_status_list", "in", self.task["sg_status_list"], "fin"], + ["project", "is", self.project], + ] - result = self._id_in_result('Task', filters, self.task['id']) + result = self._id_in_result("Task", filters, self.task["id"]) self.assertTrue(result) def test_in_relation_list_status_list(self): """ Test that 'in' relation using list (new format) works with status_list fields. """ - filters = [['sg_status_list', 'in', [self.task['sg_status_list'], 'fin']], - ['project', 'is', self.project]] + filters = [ + ["sg_status_list", "in", [self.task["sg_status_list"], "fin"]], + ["project", "is", self.project], + ] - result = self._id_in_result('Task', filters, self.task['id']) + result = self._id_in_result("Task", filters, self.task["id"]) self.assertTrue(result) def test_not_in_relation_status_list(self): """ Test that 'not_in' relation using commas (old format) works with status_list fields. """ - filters = [['sg_status_list', 'not_in', [self.task['sg_status_list'], 'fin']], - ['project', 'is', self.project]] + filters = [ + ["sg_status_list", "not_in", [self.task["sg_status_list"], "fin"]], + ["project", "is", self.project], + ] - result = self._id_in_result('Task', filters, self.task['id']) + result = self._id_in_result("Task", filters, self.task["id"]) self.assertFalse(result) def test_in_relation_comma_uuid(self): """ Test that 'in' relation using commas (old format) works with uuid fields. """ - filters = [['uuid', 'in', self.local_storage['uuid'], ]] + filters = [ + [ + "uuid", + "in", + self.local_storage["uuid"], + ] + ] - result = self._id_in_result('LocalStorage', filters, self.local_storage['id']) + result = self._id_in_result("LocalStorage", filters, self.local_storage["id"]) self.assertTrue(result) def test_in_relation_list_uuid(self): """ Test that 'in' relation using list (new format) works with uuid fields. """ - filters = [['uuid', 'in', [self.local_storage['uuid'], ]]] + filters = [ + [ + "uuid", + "in", + [ + self.local_storage["uuid"], + ], + ] + ] - result = self._id_in_result('LocalStorage', filters, self.local_storage['id']) + result = self._id_in_result("LocalStorage", filters, self.local_storage["id"]) self.assertTrue(result) def test_not_in_relation_uuid(self): """ Test that 'not_in' relation using commas (old format) works with uuid fields. """ - filters = [['uuid', 'not_in', [self.local_storage['uuid'], ]]] + filters = [ + [ + "uuid", + "not_in", + [ + self.local_storage["uuid"], + ], + ] + ] - result = self._id_in_result('LocalStorage', filters, self.local_storage['id']) + result = self._id_in_result("LocalStorage", filters, self.local_storage["id"]) self.assertFalse(result) def test_find_in(self): """Test use of 'in' relation with find.""" # id # old comma seperated format - filters = [['id', 'in', self.project['id'], 99999]] - projects = self.sg.find('Project', filters) + filters = [["id", "in", self.project["id"], 99999]] + projects = self.sg.find("Project", filters) # can't use 'any' in py 2.4 match = False for project in projects: - if project['id'] == self.project['id']: + if project["id"] == self.project["id"]: match = True self.assertTrue(match) # new list format - filters = [['id', 'in', [self.project['id'], 99999]]] - projects = self.sg.find('Project', filters) + filters = [["id", "in", [self.project["id"], 99999]]] + projects = self.sg.find("Project", filters) # can't use 'any' in py 2.4 match = False for project in projects: - if project['id'] == self.project['id']: + if project["id"] == self.project["id"]: match = True self.assertTrue(match) # text field - filters = [['name', 'in', [self.project['name'], 'fake project name']]] - projects = self.sg.find('Project', filters) + filters = [["name", "in", [self.project["name"], "fake project name"]]] + projects = self.sg.find("Project", filters) project = projects[0] - self.assertEqual(self.project['id'], project['id']) + self.assertEqual(self.project["id"], project["id"]) def test_unsupported_filters(self): - self.assertRaises(shotgun_api3.Fault, self.sg.find_one, 'Shot', - [['image', 'is_not', [{"type": "Thumbnail", "id": 9}]]]) - self.assertRaises(shotgun_api3.Fault, self.sg.find_one, 'HumanUser', [['password_proxy', 'is_not', [None]]]) - self.assertRaises(shotgun_api3.Fault, self.sg.find_one, 'EventLogEntry', [['meta', 'is_not', [None]]]) - self.assertRaises(shotgun_api3.Fault, self.sg.find_one, 'Revision', [['meta', 'attachment', [None]]]) + self.assertRaises( + shotgun_api3.Fault, + self.sg.find_one, + "Shot", + [["image", "is_not", [{"type": "Thumbnail", "id": 9}]]], + ) + self.assertRaises( + shotgun_api3.Fault, + self.sg.find_one, + "HumanUser", + [["password_proxy", "is_not", [None]]], + ) + self.assertRaises( + shotgun_api3.Fault, + self.sg.find_one, + "EventLogEntry", + [["meta", "is_not", [None]]], + ) + self.assertRaises( + shotgun_api3.Fault, + self.sg.find_one, + "Revision", + [["meta", "attachment", [None]]], + ) def test_zero_is_not_none(self): - '''Test the zero and None are differentiated using "is_not" filter. - Ticket #25127 - ''' + """Test the zero and None are differentiated using "is_not" filter. + Ticket #25127 + """ # Create a number field if it doesn't already exist - num_field = 'sg_api_tests_number_field' - if num_field not in list(self.sg.schema_field_read('Asset').keys()): - self.sg.schema_field_create('Asset', 'number', num_field.replace('sg_', '').replace('_', ' ')) + num_field = "sg_api_tests_number_field" + if num_field not in list(self.sg.schema_field_read("Asset").keys()): + self.sg.schema_field_create( + "Asset", "number", num_field.replace("sg_", "").replace("_", " ") + ) # Set to None - self.sg.update('Asset', self.asset['id'], {num_field: None}) + self.sg.update("Asset", self.asset["id"], {num_field: None}) # Should be filtered out - result = self.sg.find('Asset', [['id', 'is', self.asset['id']], [num_field, 'is_not', None]], [num_field]) + result = self.sg.find( + "Asset", + [["id", "is", self.asset["id"]], [num_field, "is_not", None]], + [num_field], + ) self.assertEqual([], result) # Set it to zero - self.sg.update('Asset', self.asset['id'], {num_field: 0}) + self.sg.update("Asset", self.asset["id"], {num_field: 0}) # Should not be filtered out - result = self.sg.find_one('Asset', [['id', 'is', self.asset['id']], [num_field, 'is_not', None]], [num_field]) + result = self.sg.find_one( + "Asset", + [["id", "is", self.asset["id"]], [num_field, "is_not", None]], + [num_field], + ) self.assertFalse(result is None) # Set it to some other number - self.sg.update('Asset', self.asset['id'], {num_field: 1}) + self.sg.update("Asset", self.asset["id"], {num_field: 1}) # Should not be filtered out - result = self.sg.find_one('Asset', [['id', 'is', self.asset['id']], [num_field, 'is_not', None]], [num_field]) + result = self.sg.find_one( + "Asset", + [["id", "is", self.asset["id"]], [num_field, "is_not", None]], + [num_field], + ) self.assertFalse(result is None) def test_include_archived_projects(self): if self.sg.server_caps.version > (5, 3, 13): # Ticket #25082 - result = self.sg.find_one('Shot', [['id', 'is', self.shot['id']]]) - self.assertEqual(self.shot['id'], result['id']) + result = self.sg.find_one("Shot", [["id", "is", self.shot["id"]]]) + self.assertEqual(self.shot["id"], result["id"]) # archive project - self.sg.update('Project', self.project['id'], {'archived': True}) + self.sg.update("Project", self.project["id"], {"archived": True}) # setting defaults to True, so we should get result - result = self.sg.find_one('Shot', [['id', 'is', self.shot['id']]]) - self.assertEqual(self.shot['id'], result['id']) + result = self.sg.find_one("Shot", [["id", "is", self.shot["id"]]]) + self.assertEqual(self.shot["id"], result["id"]) - result = self.sg.find_one('Shot', [['id', 'is', self.shot['id']]], include_archived_projects=False) + result = self.sg.find_one( + "Shot", [["id", "is", self.shot["id"]]], include_archived_projects=False + ) self.assertEqual(None, result) # unarchive project - self.sg.update('Project', self.project['id'], {'archived': False}) + self.sg.update("Project", self.project["id"], {"archived": False}) class TestFollow(base.LiveTestBase): def test_follow_unfollow(self): - '''Test follow method''' + """Test follow method""" if not self.sg.server_caps.version or self.sg.server_caps.version < (5, 1, 22): return @@ -1758,13 +2005,13 @@ def test_follow_unfollow(self): project=self.project, ) as shot: result = self.sg.follow(human_user, shot) - assert(result['followed']) + assert result["followed"] result = self.sg.unfollow(human_user, shot) - assert(result['unfollowed']) + assert result["unfollowed"] def test_followers(self): - '''Test followers method''' + """Test followers method""" if not self.sg.server_caps.version or self.sg.server_caps.version < (5, 1, 22): return @@ -1777,18 +2024,21 @@ def test_followers(self): project=self.project, ) as shot: result = self.sg.follow(human_user, shot) - assert(result['followed']) + assert result["followed"] result = self.sg.followers(shot) self.assertEqual(1, len(result)) - self.assertEqual(human_user['id'], result[0]['id']) + self.assertEqual(human_user["id"], result[0]["id"]) def test_following(self): - '''Test following method''' + """Test following method""" if not self.sg.server_caps.version or self.sg.server_caps.version < (7, 0, 12): - warnings.warn("Test bypassed because PTR server used does not support this feature.", FutureWarning) + warnings.warn( + "Test bypassed because PTR server used does not support this feature.", + FutureWarning, + ) return with self.gen_entity( @@ -1802,14 +2052,14 @@ def test_following(self): project=self.project, ) as task: result = self.sg.follow(human_user, shot) - assert(result['followed']) + assert result["followed"] result = self.sg.following(human_user) self.assertEqual(1, len(result)) result = self.sg.follow(human_user, task) - assert(result['followed']) + assert result["followed"] result = self.sg.following(human_user) @@ -1819,24 +2069,32 @@ def test_following(self): result = self.sg.following(human_user, entity_type="Shot") self.assertEqual(1, len(result)) - shot_project_id = self.sg.find_one("Shot", - [["id", "is", shot["id"]]], - ["project.Project.id"])["project.Project.id"] - task_project_id = self.sg.find_one("Task", - [["id", "is", task["id"]]], - ["project.Project.id"])["project.Project.id"] + shot_project_id = self.sg.find_one( + "Shot", [["id", "is", shot["id"]]], ["project.Project.id"] + )["project.Project.id"] + task_project_id = self.sg.find_one( + "Task", [["id", "is", task["id"]]], ["project.Project.id"] + )["project.Project.id"] project_count = 2 if shot_project_id == task_project_id else 1 - result = self.sg.following(human_user, project={"type": "Project", "id": shot_project_id}) + result = self.sg.following( + human_user, project={"type": "Project", "id": shot_project_id} + ) self.assertEqual(project_count, len(result)) - result = self.sg.following(human_user, project={"type": "Project", "id": task_project_id}) + result = self.sg.following( + human_user, project={"type": "Project", "id": task_project_id} + ) self.assertEqual(project_count, len(result)) - result = self.sg.following(human_user, - project={"type": "Project", "id": shot_project_id}, - entity_type="Shot") + result = self.sg.following( + human_user, + project={"type": "Project", "id": shot_project_id}, + entity_type="Shot", + ) self.assertEqual(1, len(result)) - result = self.sg.following(human_user, - project={"type": "Project", "id": task_project_id}, - entity_type="Task") + result = self.sg.following( + human_user, + project={"type": "Project", "id": task_project_id}, + entity_type="Task", + ) self.assertEqual(1, len(result)) @@ -1846,9 +2104,9 @@ def setUp(self): super(TestErrors, self).setUp(auth_mode) def test_bad_auth(self): - '''test_bad_auth invalid script name or api key raises fault''' + """test_bad_auth invalid script name or api key raises fault""" server_url = self.config.server_url - script_name = 'not_real_script_name' + script_name = "not_real_script_name" api_key = self.config.api_key login = self.config.human_login password = self.config.human_password @@ -1857,48 +2115,94 @@ def test_bad_auth(self): # Test various combinations of illegal arguments self.assertRaises(ValueError, shotgun_api3.Shotgun, server_url) self.assertRaises(ValueError, shotgun_api3.Shotgun, server_url, None, api_key) - self.assertRaises(ValueError, shotgun_api3.Shotgun, server_url, script_name, None) - self.assertRaises(ValueError, shotgun_api3.Shotgun, server_url, script_name, - api_key, login=login, password=password) + self.assertRaises( + ValueError, shotgun_api3.Shotgun, server_url, script_name, None + ) + self.assertRaises( + ValueError, + shotgun_api3.Shotgun, + server_url, + script_name, + api_key, + login=login, + password=password, + ) self.assertRaises(ValueError, shotgun_api3.Shotgun, server_url, login=login) - self.assertRaises(ValueError, shotgun_api3.Shotgun, server_url, password=password) - self.assertRaises(ValueError, shotgun_api3.Shotgun, server_url, script_name, login=login, password=password) - self.assertRaises(ValueError, shotgun_api3.Shotgun, server_url, login=login, auth_token=auth_token) - self.assertRaises(ValueError, shotgun_api3.Shotgun, server_url, password=password, auth_token=auth_token) - self.assertRaises(ValueError, shotgun_api3.Shotgun, server_url, script_name, login=login, - password=password, auth_token=auth_token) - self.assertRaises(ValueError, shotgun_api3.Shotgun, server_url, api_key=api_key, login=login, - password=password, auth_token=auth_token) + self.assertRaises( + ValueError, shotgun_api3.Shotgun, server_url, password=password + ) + self.assertRaises( + ValueError, + shotgun_api3.Shotgun, + server_url, + script_name, + login=login, + password=password, + ) + self.assertRaises( + ValueError, + shotgun_api3.Shotgun, + server_url, + login=login, + auth_token=auth_token, + ) + self.assertRaises( + ValueError, + shotgun_api3.Shotgun, + server_url, + password=password, + auth_token=auth_token, + ) + self.assertRaises( + ValueError, + shotgun_api3.Shotgun, + server_url, + script_name, + login=login, + password=password, + auth_token=auth_token, + ) + self.assertRaises( + ValueError, + shotgun_api3.Shotgun, + server_url, + api_key=api_key, + login=login, + password=password, + auth_token=auth_token, + ) # Test failed authentications sg = shotgun_api3.Shotgun(server_url, script_name, api_key) - self.assertRaises(shotgun_api3.AuthenticationFault, sg.find_one, 'Shot', []) + self.assertRaises(shotgun_api3.AuthenticationFault, sg.find_one, "Shot", []) script_name = self.config.script_name - api_key = 'notrealapikey' + api_key = "notrealapikey" sg = shotgun_api3.Shotgun(server_url, script_name, api_key) - self.assertRaises(shotgun_api3.AuthenticationFault, sg.find_one, 'Shot', []) + self.assertRaises(shotgun_api3.AuthenticationFault, sg.find_one, "Shot", []) - sg = shotgun_api3.Shotgun(server_url, login=login, password='not a real password') - self.assertRaises(shotgun_api3.AuthenticationFault, sg.find_one, 'Shot', []) + sg = shotgun_api3.Shotgun( + server_url, login=login, password="not a real password" + ) + self.assertRaises(shotgun_api3.AuthenticationFault, sg.find_one, "Shot", []) # This may trigger an account lockdown. Make sure it is not locked anymore. user = self.sg.find_one("HumanUser", [["login", "is", login]]) self.sg.update("HumanUser", user["id"], {"locked_until": None}) - @patch('shotgun_api3.shotgun.Http.request') + @patch("shotgun_api3.shotgun.Http.request") def test_status_not_200(self, mock_request): response = MagicMock(name="response mock", spec=dict) response.status = 300 - response.reason = 'reason' + response.reason = "reason" mock_request.return_value = (response, {}) - self.assertRaises(shotgun_api3.ProtocolError, self.sg.find_one, 'Shot', []) + self.assertRaises(shotgun_api3.ProtocolError, self.sg.find_one, "Shot", []) - @patch('shotgun_api3.shotgun.Http.request') + @patch("shotgun_api3.shotgun.Http.request") def test_make_call_retry(self, mock_request): response = MagicMock(name="response mock", spec=dict) response.status = 200 - response.reason = 'reason' + response.reason = "reason" mock_request.return_value = (response, {}) bak_rpc_attempt_interval = self.sg.config.rpc_attempt_interval @@ -1907,15 +2211,13 @@ def test_make_call_retry(self, mock_request): # First: make the request raise a consistent exception mock_request.side_effect = Exception("not working") with self.assertLogs( - 'shotgun_api3', level='DEBUG' - ) as cm1, self.assertRaises( - Exception - ) as cm2: + "shotgun_api3", level="DEBUG" + ) as cm1, self.assertRaises(Exception) as cm2: self.sg.info() self.assertEqual(cm2.exception.args[0], "not working") log_content = "\n".join(cm1.output) - for i in [1,2]: + for i in [1, 2]: self.assertIn( f"Request failed, attempt {i} of 3. Retrying", log_content, @@ -1929,7 +2231,7 @@ def test_make_call_retry(self, mock_request): # retry works def my_side_effect(*args, **kwargs): try: - if my_side_effect.counter<1: + if my_side_effect.counter < 1: raise Exception("not working") return mock.DEFAULT @@ -1938,7 +2240,7 @@ def my_side_effect(*args, **kwargs): my_side_effect.counter = 0 mock_request.side_effect = my_side_effect - with self.assertLogs('shotgun_api3', level='DEBUG') as cm: + with self.assertLogs("shotgun_api3", level="DEBUG") as cm: self.assertIsInstance( self.sg.info(), dict, @@ -1957,7 +2259,7 @@ def my_side_effect(*args, **kwargs): # Last: raise a SSLEOFError exception - SG-34910 def my_side_effect2(*args, **kwargs): try: - if my_side_effect2.counter<1: + if my_side_effect2.counter < 1: raise ssl.SSLEOFError( "EOF occurred in violation of protocol (_ssl.c:2426)" ) @@ -1969,7 +2271,7 @@ def my_side_effect2(*args, **kwargs): my_side_effect2.counter = 0 mock_request.side_effect = my_side_effect2 - with self.assertLogs('shotgun_api3', level='DEBUG') as cm: + with self.assertLogs("shotgun_api3", level="DEBUG") as cm: self.assertIsInstance( self.sg.info(), dict, @@ -1988,7 +2290,7 @@ def my_side_effect2(*args, **kwargs): finally: self.sg.config.rpc_attempt_interval = bak_rpc_attempt_interval - @patch('shotgun_api3.shotgun.Http.request') + @patch("shotgun_api3.shotgun.Http.request") def test_sha2_error(self, mock_request): # Simulate the exception raised with SHA-2 errors mock_request.side_effect = ShotgunSSLError( @@ -2028,7 +2330,7 @@ def test_sha2_error(self, mock_request): if original_env_val is not None: os.environ["SHOTGUN_FORCE_CERTIFICATE_VALIDATION"] = original_env_val - @patch('shotgun_api3.shotgun.Http.request') + @patch("shotgun_api3.shotgun.Http.request") def test_sha2_error_with_strict(self, mock_request): # Simulate the exception raised with SHA-2 errors mock_request.side_effect = ShotgunSSLError( @@ -2059,17 +2361,17 @@ def test_sha2_error_with_strict(self, mock_request): if original_env_val is not None: os.environ["SHOTGUN_FORCE_CERTIFICATE_VALIDATION"] = original_env_val - @patch.object(urllib.request.OpenerDirector, 'open') + @patch.object(urllib.request.OpenerDirector, "open") def test_sanitized_auth_params(self, mock_open): # Simulate the server blowing up and giving us a 500 error - mock_open.side_effect = urllib.error.HTTPError('url', 500, 'message', {}, None) + mock_open.side_effect = urllib.error.HTTPError("url", 500, "message", {}, None) this_dir, _ = os.path.split(__file__) thumbnail_path = os.path.abspath(os.path.join(this_dir, "sg_logo.jpg")) try: # Try to upload a bogus file - self.sg.upload('Note', 1234, thumbnail_path) + self.sg.upload("Note", 1234, thumbnail_path) except shotgun_api3.ShotgunError as e: self.assertFalse(str(self.api_key) in str(e)) return @@ -2084,20 +2386,39 @@ def test_upload_empty_file(self): """ this_dir, _ = os.path.split(__file__) path = os.path.abspath(os.path.expanduser(os.path.join(this_dir, "empty.txt"))) - self.assertRaises(shotgun_api3.ShotgunError, self.sg.upload, 'Version', 123, path) - self.assertRaises(shotgun_api3.ShotgunError, self.sg.upload_thumbnail, 'Version', 123, path) - self.assertRaises(shotgun_api3.ShotgunError, self.sg.upload_filmstrip_thumbnail, 'Version', - 123, path) + self.assertRaises( + shotgun_api3.ShotgunError, self.sg.upload, "Version", 123, path + ) + self.assertRaises( + shotgun_api3.ShotgunError, self.sg.upload_thumbnail, "Version", 123, path + ) + self.assertRaises( + shotgun_api3.ShotgunError, + self.sg.upload_filmstrip_thumbnail, + "Version", + 123, + path, + ) def test_upload_missing_file(self): """ Test uploading an missing file raises an error. """ path = "/path/to/nowhere/foo.txt" - self.assertRaises(shotgun_api3.ShotgunError, self.sg.upload, 'Version', 123, path) - self.assertRaises(shotgun_api3.ShotgunError, self.sg.upload_thumbnail, 'Version', 123, path) - self.assertRaises(shotgun_api3.ShotgunError, self.sg.upload_filmstrip_thumbnail, 'Version', - 123, path) + self.assertRaises( + shotgun_api3.ShotgunError, self.sg.upload, "Version", 123, path + ) + self.assertRaises( + shotgun_api3.ShotgunError, self.sg.upload_thumbnail, "Version", 123, path + ) + self.assertRaises( + shotgun_api3.ShotgunError, + self.sg.upload_filmstrip_thumbnail, + "Version", + 123, + path, + ) + # def test_malformed_response(self): # # TODO ResponseError @@ -2109,9 +2430,9 @@ def setUp(self): super(TestScriptUserSudoAuth, self).setUp() self.sg.update( - 'HumanUser', - self.human_user['id'], - {'projects': [self.project]}, + "HumanUser", + self.human_user["id"], + {"projects": [self.project]}, ) def test_user_is_creator(self): @@ -2122,30 +2443,32 @@ def test_user_is_creator(self): if not self.sg.server_caps.version or self.sg.server_caps.version < (5, 3, 12): return - x = shotgun_api3.Shotgun(self.config.server_url, - http_proxy=self.config.http_proxy, - sudo_as_login=self.config.human_login, - **self.auth_args) + x = shotgun_api3.Shotgun( + self.config.server_url, + http_proxy=self.config.http_proxy, + sudo_as_login=self.config.human_login, + **self.auth_args, + ) data = { - 'project': self.project, - 'code': 'JohnnyApple_Design01_FaceFinal', - 'description': 'fixed rig per director final notes', - 'sg_status_list': 'na', - 'entity': self.asset, - 'user': self.human_user + "project": self.project, + "code": "JohnnyApple_Design01_FaceFinal", + "description": "fixed rig per director final notes", + "sg_status_list": "na", + "entity": self.asset, + "user": self.human_user, } version = x.create("Version", data, return_fields=["id", "created_by"]) self.assertTrue(isinstance(version, dict)) self.assertTrue("id" in version) self.assertTrue("created_by" in version) - self.assertEqual(self.config.human_name, version['created_by']['name']) + self.assertEqual(self.config.human_name, version["created_by"]["name"]) class TestHumanUserSudoAuth(base.TestBase): def setUp(self): - super(TestHumanUserSudoAuth, self).setUp('HumanUser') + super(TestHumanUserSudoAuth, self).setUp("HumanUser") def test_human_user_sudo_auth_fails(self): """ @@ -2158,18 +2481,20 @@ def test_human_user_sudo_auth_fails(self): if not self.sg.server_caps.version or self.sg.server_caps.version < (5, 3, 12): return - x = shotgun_api3.Shotgun(self.config.server_url, - login=self.config.human_login, - password=self.config.human_password, - http_proxy=self.config.http_proxy, - sudo_as_login="blah") - self.assertRaises(shotgun_api3.Fault, x.find_one, 'Shot', []) + x = shotgun_api3.Shotgun( + self.config.server_url, + login=self.config.human_login, + password=self.config.human_password, + http_proxy=self.config.http_proxy, + sudo_as_login="blah", + ) + self.assertRaises(shotgun_api3.Fault, x.find_one, "Shot", []) expected = "The user does not have permission to 'sudo':" try: - x.find_one('Shot', []) + x.find_one("Shot", []) except shotgun_api3.Fault as e: # py24 exceptions don't have message attr - if hasattr(e, 'message'): + if hasattr(e, "message"): self.assertTrue(e.message.startswith(expected)) else: self.assertTrue(e.args[0].startswith(expected)) @@ -2183,30 +2508,31 @@ class TestHumanUserAuth(base.HumanUserAuthLiveTestBase): def test_humanuser_find(self): """Called find, find_one for known entities as human user""" filters = [] - filters.append(['project', 'is', self.project]) - filters.append(['id', 'is', self.version['id']]) + filters.append(["project", "is", self.project]) + filters.append(["id", "is", self.version["id"]]) - fields = ['id'] + fields = ["id"] versions = self.sg.find("Version", filters, fields=fields) self.assertTrue(isinstance(versions, list)) version = versions[0] self.assertEqual("Version", version["type"]) - self.assertEqual(self.version['id'], version["id"]) + self.assertEqual(self.version["id"], version["id"]) version = self.sg.find_one("Version", filters, fields=fields) self.assertEqual("Version", version["type"]) - self.assertEqual(self.version['id'], version["id"]) + self.assertEqual(self.version["id"], version["id"]) def test_humanuser_upload_thumbnail_for_version(self): """simple upload thumbnail for version test as human user.""" this_dir, _ = os.path.split(__file__) - path = os.path.abspath(os.path.expanduser( - os.path.join(this_dir, "sg_logo.jpg"))) + path = os.path.abspath( + os.path.expanduser(os.path.join(this_dir, "sg_logo.jpg")) + ) # upload thumbnail - thumb_id = self.sg.upload_thumbnail("Version", self.version['id'], path) + thumb_id = self.sg.upload_thumbnail("Version", self.version["id"], path) self.assertTrue(isinstance(thumb_id, int)) # check result on version @@ -2215,17 +2541,23 @@ def test_humanuser_upload_thumbnail_for_version(self): [["id", "is", self.version["id"]]], ) - self.assertEqual(version_with_thumbnail.get('type'), 'Version') - self.assertEqual(version_with_thumbnail.get('id'), self.version['id']) + self.assertEqual(version_with_thumbnail.get("type"), "Version") + self.assertEqual(version_with_thumbnail.get("id"), self.version["id"]) h = Http(".cache") - thumb_resp, content = h.request(version_with_thumbnail.get('image'), "GET") - self.assertIn(thumb_resp['status'], ['200', '304']) - self.assertIn(thumb_resp['content-type'], ['image/jpeg', 'image/png']) + thumb_resp, content = h.request(version_with_thumbnail.get("image"), "GET") + self.assertIn(thumb_resp["status"], ["200", "304"]) + self.assertIn(thumb_resp["content-type"], ["image/jpeg", "image/png"]) # clear thumbnail - response_clear_thumbnail = self.sg.update("Version", self.version['id'], {'image': None}) - expected_clear_thumbnail = {'id': self.version['id'], 'image': None, 'type': 'Version'} + response_clear_thumbnail = self.sg.update( + "Version", self.version["id"], {"image": None} + ) + expected_clear_thumbnail = { + "id": self.version["id"], + "image": None, + "type": "Version", + } self.assertEqual(expected_clear_thumbnail, response_clear_thumbnail) @@ -2240,21 +2572,21 @@ def test_humanuser_find(self): if self.sg.server_caps.version >= (5, 4, 1): filters = [] - filters.append(['project', 'is', self.project]) - filters.append(['id', 'is', self.version['id']]) + filters.append(["project", "is", self.project]) + filters.append(["id", "is", self.version["id"]]) - fields = ['id'] + fields = ["id"] versions = self.sg.find("Version", filters, fields=fields) self.assertTrue(isinstance(versions, list)) version = versions[0] self.assertEqual("Version", version["type"]) - self.assertEqual(self.version['id'], version["id"]) + self.assertEqual(self.version["id"], version["id"]) version = self.sg.find_one("Version", filters, fields=fields) self.assertEqual("Version", version["type"]) - self.assertEqual(self.version['id'], version["id"]) + self.assertEqual(self.version["id"], version["id"]) def test_humanuser_upload_thumbnail_for_version(self): """simple upload thumbnail for version test as session based token user.""" @@ -2262,11 +2594,12 @@ def test_humanuser_upload_thumbnail_for_version(self): if self.sg.server_caps.version >= (5, 4, 1): this_dir, _ = os.path.split(__file__) - path = os.path.abspath(os.path.expanduser( - os.path.join(this_dir, "sg_logo.jpg"))) + path = os.path.abspath( + os.path.expanduser(os.path.join(this_dir, "sg_logo.jpg")) + ) # upload thumbnail - thumb_id = self.sg.upload_thumbnail("Version", self.version['id'], path) + thumb_id = self.sg.upload_thumbnail("Version", self.version["id"], path) self.assertTrue(isinstance(thumb_id, int)) # check result on version @@ -2275,17 +2608,23 @@ def test_humanuser_upload_thumbnail_for_version(self): [["id", "is", self.version["id"]]], ) - self.assertEqual(version_with_thumbnail.get('type'), 'Version') - self.assertEqual(version_with_thumbnail.get('id'), self.version['id']) + self.assertEqual(version_with_thumbnail.get("type"), "Version") + self.assertEqual(version_with_thumbnail.get("id"), self.version["id"]) h = Http(".cache") - thumb_resp, content = h.request(version_with_thumbnail.get('image'), "GET") - self.assertIn(thumb_resp['status'], ['200', '304']) - self.assertIn(thumb_resp['content-type'], ['image/jpeg', 'image/png']) + thumb_resp, content = h.request(version_with_thumbnail.get("image"), "GET") + self.assertIn(thumb_resp["status"], ["200", "304"]) + self.assertIn(thumb_resp["content-type"], ["image/jpeg", "image/png"]) # clear thumbnail - response_clear_thumbnail = self.sg.update("Version", self.version['id'], {'image': None}) - expected_clear_thumbnail = {'id': self.version['id'], 'image': None, 'type': 'Version'} + response_clear_thumbnail = self.sg.update( + "Version", self.version["id"], {"image": None} + ) + expected_clear_thumbnail = { + "id": self.version["id"], + "image": None, + "type": "Version", + } self.assertEqual(expected_clear_thumbnail, response_clear_thumbnail) @@ -2295,64 +2634,103 @@ def test_logged_in_user(self): if self.sg.server_caps.version and self.sg.server_caps.version < (5, 3, 20): return - sg = shotgun_api3.Shotgun(self.config.server_url, - login=self.config.human_login, - password=self.config.human_password, - http_proxy=self.config.http_proxy) + sg = shotgun_api3.Shotgun( + self.config.server_url, + login=self.config.human_login, + password=self.config.human_password, + http_proxy=self.config.http_proxy, + ) sg.update_project_last_accessed(self.project) - initial = sg.find_one('Project', [['id', 'is', self.project['id']]], ['last_accessed_by_current_user']) + initial = sg.find_one( + "Project", + [["id", "is", self.project["id"]]], + ["last_accessed_by_current_user"], + ) # Make sure time has elapsed so there is a difference between the two time stamps. time.sleep(2) sg.update_project_last_accessed(self.project) - current = sg.find_one('Project', [['id', 'is', self.project['id']]], ['last_accessed_by_current_user']) + current = sg.find_one( + "Project", + [["id", "is", self.project["id"]]], + ["last_accessed_by_current_user"], + ) self.assertNotEqual(initial, current) # it's possible initial is None - assert(initial['last_accessed_by_current_user'] < current['last_accessed_by_current_user']) + assert ( + initial["last_accessed_by_current_user"] + < current["last_accessed_by_current_user"] + ) def test_pass_in_user(self): if self.sg.server_caps.version and self.sg.server_caps.version < (5, 3, 20): return - sg = shotgun_api3.Shotgun(self.config.server_url, - login=self.config.human_login, - password=self.config.human_password, - http_proxy=self.config.http_proxy) + sg = shotgun_api3.Shotgun( + self.config.server_url, + login=self.config.human_login, + password=self.config.human_password, + http_proxy=self.config.http_proxy, + ) - initial = sg.find_one('Project', [['id', 'is', self.project['id']]], ['last_accessed_by_current_user']) + initial = sg.find_one( + "Project", + [["id", "is", self.project["id"]]], + ["last_accessed_by_current_user"], + ) time.sleep(1) # this instance of the api is not logged in as a user self.sg.update_project_last_accessed(self.project, user=self.human_user) - current = sg.find_one('Project', [['id', 'is', self.project['id']]], ['last_accessed_by_current_user']) + current = sg.find_one( + "Project", + [["id", "is", self.project["id"]]], + ["last_accessed_by_current_user"], + ) self.assertNotEqual(initial, current) # it's possible initial is None if initial: - assert(initial['last_accessed_by_current_user'] < current['last_accessed_by_current_user']) + assert ( + initial["last_accessed_by_current_user"] + < current["last_accessed_by_current_user"] + ) def test_sudo_as_user(self): if self.sg.server_caps.version and self.sg.server_caps.version < (5, 3, 20): return - sg = shotgun_api3.Shotgun(self.config.server_url, - http_proxy=self.config.http_proxy, - sudo_as_login=self.config.human_login, - **self.auth_args) + sg = shotgun_api3.Shotgun( + self.config.server_url, + http_proxy=self.config.http_proxy, + sudo_as_login=self.config.human_login, + **self.auth_args, + ) - initial = sg.find_one('Project', [['id', 'is', self.project['id']]], ['last_accessed_by_current_user']) + initial = sg.find_one( + "Project", + [["id", "is", self.project["id"]]], + ["last_accessed_by_current_user"], + ) time.sleep(1) sg.update_project_last_accessed(self.project) - current = sg.find_one('Project', [['id', 'is', self.project['id']]], ['last_accessed_by_current_user']) + current = sg.find_one( + "Project", + [["id", "is", self.project["id"]]], + ["last_accessed_by_current_user"], + ) self.assertNotEqual(initial, current) # it's possible initial is None if initial: - assert(initial['last_accessed_by_current_user'] < current['last_accessed_by_current_user']) + assert ( + initial["last_accessed_by_current_user"] + < current["last_accessed_by_current_user"] + ) class TestActivityStream(base.LiveTestBase): @@ -2364,36 +2742,51 @@ def setUp(self): super(TestActivityStream, self).setUp() self._prefix = uuid.uuid4().hex - self._shot = self.sg.create("Shot", {"code": "%s activity stream test" % self._prefix, - "project": self.project}) + self._shot = self.sg.create( + "Shot", + {"code": "%s activity stream test" % self._prefix, "project": self.project}, + ) - self._note = self.sg.create("Note", {"content": "Test!", - "project": self.project, - "note_links": [self._shot]}) + self._note = self.sg.create( + "Note", + {"content": "Test!", "project": self.project, "note_links": [self._shot]}, + ) # check that if the created_by is a script user, we want to ensure # that event log generation is enabled for this user. If it has been # disabled, these tests will fail because the activity stream is # connected to events. In this case, print a warning to the user - d = self.sg.find_one("Shot", - [["id", "is", self._shot["id"]]], - ["created_by.ApiUser.generate_event_log_entries"]) + d = self.sg.find_one( + "Shot", + [["id", "is", self._shot["id"]]], + ["created_by.ApiUser.generate_event_log_entries"], + ) if d["created_by.ApiUser.generate_event_log_entries"] is False: # events are turned off! warn the user - print("WARNING! Looks like the script user that is running these " - "tests has got the generate event log entries setting set to " - "off. This will cause the activity stream tests to fail. " - "Please enable event log generation for the script user.") + print( + "WARNING! Looks like the script user that is running these " + "tests has got the generate event log entries setting set to " + "off. This will cause the activity stream tests to fail. " + "Please enable event log generation for the script user." + ) def tearDown(self): batch_data = [] - batch_data.append({"request_type": "delete", - "entity_type": self._note["type"], - "entity_id": self._note["id"]}) - batch_data.append({"request_type": "delete", - "entity_type": self._shot["type"], - "entity_id": self._shot["id"]}) + batch_data.append( + { + "request_type": "delete", + "entity_type": self._note["type"], + "entity_id": self._note["id"], + } + ) + batch_data.append( + { + "request_type": "delete", + "entity_type": self._shot["type"], + "entity_id": self._shot["id"], + } + ) self.sg.batch(batch_data) super(TestActivityStream, self).tearDown() @@ -2406,14 +2799,15 @@ def test_simple(self): if not self.sg.server_caps.version or self.sg.server_caps.version < (6, 2, 0): return - result = self.sg.activity_stream_read(self._shot["type"], - self._shot["id"]) + result = self.sg.activity_stream_read(self._shot["type"], self._shot["id"]) - expected_keys = ["earliest_update_id", - "entity_id", - "entity_type", - "latest_update_id", - "updates"] + expected_keys = [ + "earliest_update_id", + "entity_id", + "entity_type", + "latest_update_id", + "updates", + ] self.assertEqual(set(expected_keys), set(result.keys())) self.assertEqual(len(result["updates"]), 2) @@ -2428,9 +2822,9 @@ def test_limit(self): if not self.sg.server_caps.version or self.sg.server_caps.version < (6, 2, 0): return - result = self.sg.activity_stream_read(self._shot["type"], - self._shot["id"], - limit=1) + result = self.sg.activity_stream_read( + self._shot["type"], self._shot["id"], limit=1 + ) self.assertEqual(len(result["updates"]), 1) self.assertEqual(result["updates"][0]["update_type"], "create") @@ -2444,25 +2838,22 @@ def test_extra_fields(self): if not self.sg.server_caps.version or self.sg.server_caps.version < (6, 2, 0): return - result = self.sg.activity_stream_read(self._shot["type"], - self._shot["id"], - entity_fields={"Shot": ["created_by.HumanUser.image"], - "Note": ["content"]}) + result = self.sg.activity_stream_read( + self._shot["type"], + self._shot["id"], + entity_fields={"Shot": ["created_by.HumanUser.image"], "Note": ["content"]}, + ) self.assertEqual(len(result["updates"]), 2) - self.assertEqual(set(result["updates"][0]["primary_entity"].keys()), - set(["content", - "id", - "name", - "status", - "type"])) + self.assertEqual( + set(result["updates"][0]["primary_entity"].keys()), + set(["content", "id", "name", "status", "type"]), + ) - self.assertEqual(set(result["updates"][1]["primary_entity"].keys()), - set(["created_by.HumanUser.image", - "id", - "name", - "status", - "type"])) + self.assertEqual( + set(result["updates"][1]["primary_entity"].keys()), + set(["created_by.HumanUser.image", "id", "name", "status", "type"]), + ) class TestNoteThreadRead(base.LiveTestBase): @@ -2480,14 +2871,16 @@ def setUp(self): def _check_note(self, data, note_id, additional_fields): # check the expected fields - expected_fields = set(["content", "created_at", "created_by", "id", "type"] + additional_fields) + expected_fields = set( + ["content", "created_at", "created_by", "id", "type"] + additional_fields + ) self.assertEqual(expected_fields, set(data.keys())) # check that the data matches the data we get from a find call - note_data = self.sg.find_one("Note", - [["id", "is", note_id]], - list(expected_fields)) + note_data = self.sg.find_one( + "Note", [["id", "is", note_id]], list(expected_fields) + ) # remove images before comparison if ( "created_by.HumanUser.image" in note_data @@ -2500,13 +2893,15 @@ def _check_note(self, data, note_id, additional_fields): def _check_reply(self, data, reply_id, additional_fields): # check the expected fields - expected_fields = set(["content", "created_at", "user", "id", "type"] + additional_fields) + expected_fields = set( + ["content", "created_at", "user", "id", "type"] + additional_fields + ) self.assertEqual(expected_fields, set(data.keys())) # check that the data matches the data we get from a find call - reply_data = self.sg.find_one("Reply", - [["id", "is", reply_id]], - list(expected_fields)) + reply_data = self.sg.find_one( + "Reply", [["id", "is", reply_id]], list(expected_fields) + ) # the reply stream adds an image to the user fields in order # to include thumbnails for users, so remove this before we compare @@ -2517,13 +2912,15 @@ def _check_reply(self, data, reply_id, additional_fields): def _check_attachment(self, data, attachment_id, additional_fields): # check the expected fields - expected_fields = set(["created_at", "created_by", "id", "type"] + additional_fields) + expected_fields = set( + ["created_at", "created_by", "id", "type"] + additional_fields + ) self.assertEqual(expected_fields, set(data.keys())) # check that the data matches the data we get from a find call - attachment_data = self.sg.find_one("Attachment", - [["id", "is", attachment_id]], - list(expected_fields)) + attachment_data = self.sg.find_one( + "Attachment", [["id", "is", attachment_id]], list(expected_fields) + ) # remove images before comparison if "this_file" in attachment_data and "this_file" in data: @@ -2551,21 +2948,25 @@ def test_simple(self): # reply. For this, make sure that there is a thumbnail # associated with the current user - d = self.sg.find_one("Note", - [["id", "is", note["id"]]], - ["created_by", f"created_by.{user_entity}.image"]) + d = self.sg.find_one( + "Note", + [["id", "is", note["id"]]], + ["created_by", f"created_by.{user_entity}.image"], + ) current_thumbnail = d[f"created_by.{user_entity}.image"] if current_thumbnail is None: # upload thumbnail - self.sg.upload_thumbnail(user_entity, - d["created_by"]["id"], - self._thumbnail_path) + self.sg.upload_thumbnail( + user_entity, d["created_by"]["id"], self._thumbnail_path + ) - d = self.sg.find_one("Note", - [["id", "is", note["id"]]], - ["created_by", f"created_by.{user_entity}.image"]) + d = self.sg.find_one( + "Note", + [["id", "is", note["id"]]], + ["created_by", f"created_by.{user_entity}.image"], + ) current_thumbnail = d[f"created_by.{user_entity}.image"] @@ -2587,8 +2988,10 @@ def test_simple(self): reply_thumb = result[1]["user"]["image"] url_obj_a = urllib.parse.urlparse(current_thumbnail) url_obj_b = urllib.parse.urlparse(reply_thumb) - self.assertEqual("%s/%s" % (url_obj_a.netloc, url_obj_a.path), - "%s/%s" % (url_obj_b.netloc, url_obj_b.path),) + self.assertEqual( + "%s/%s" % (url_obj_a.netloc, url_obj_a.path), + "%s/%s" % (url_obj_b.netloc, url_obj_b.path), + ) # and check ther rest of the data self._check_note(result[0], note["id"], additional_fields=[]) @@ -2615,18 +3018,25 @@ def test_complex(self): return additional_fields = { - "Note": ["created_by.HumanUser.image", - "addressings_to", - "playlist", - "user"], + "Note": [ + "created_by.HumanUser.image", + "addressings_to", + "playlist", + "user", + ], "Reply": ["content"], - "Attachment": ["this_file"] + "Attachment": ["this_file"], } # create note - note = self.sg.create("Note", {"content": "Test!", - "project": self.project, - "addressings_to": [self.human_user]}) + note = self.sg.create( + "Note", + { + "content": "Test!", + "project": self.project, + "addressings_to": [self.human_user], + }, + ) # get thread result = self.sg.note_thread_read(note["id"], additional_fields) @@ -2652,7 +3062,9 @@ def test_complex(self): self._check_note(result[0], note["id"], additional_fields["Note"]) self._check_reply(result[1], reply["id"], additional_fields["Reply"]) - self._check_attachment(result[2], attachment_id, additional_fields["Attachment"]) + self._check_attachment( + result[2], attachment_id, additional_fields["Attachment"] + ) class TestTextSearch(base.LiveTestBase): @@ -2668,14 +3080,16 @@ def setUp(self): batch_data = [] for i in range(5): - data = {"code": "%s Text Search %s" % (self._prefix, i), - "project": self.project} - batch_data.append({"request_type": "create", - "entity_type": "Shot", - "data": data}) - batch_data.append({"request_type": "create", - "entity_type": "Asset", - "data": data}) + data = { + "code": "%s Text Search %s" % (self._prefix, i), + "project": self.project, + } + batch_data.append( + {"request_type": "create", "entity_type": "Shot", "data": data} + ) + batch_data.append( + {"request_type": "create", "entity_type": "Asset", "data": data} + ) data = self.sg.batch(batch_data) self._shot_ids = [x["id"] for x in data if x["type"] == "Shot"] @@ -2686,13 +3100,17 @@ def tearDown(self): # clean up batch_data = [] for shot_id in self._shot_ids: - batch_data.append({"request_type": "delete", - "entity_type": "Shot", - "entity_id": shot_id}) + batch_data.append( + {"request_type": "delete", "entity_type": "Shot", "entity_id": shot_id} + ) for asset_id in self._asset_ids: - batch_data.append({"request_type": "delete", - "entity_type": "Asset", - "entity_id": asset_id}) + batch_data.append( + { + "request_type": "delete", + "entity_type": "Asset", + "entity_id": asset_id, + } + ) self.sg.batch(batch_data) super(TestTextSearch, self).tearDown() @@ -2724,7 +3142,9 @@ def test_limit(self): if not self.sg.server_caps.version or self.sg.server_caps.version < (6, 2, 0): return - result = self.sg.text_search("%s Text Search" % self._prefix, {"Shot": []}, limit=3) + result = self.sg.text_search( + "%s Text Search" % self._prefix, {"Shot": []}, limit=3 + ) matches = result["matches"] self.assertEqual(len(matches), 3) @@ -2735,8 +3155,9 @@ def test_entity_filter(self): if not self.sg.server_caps.version or self.sg.server_caps.version < (6, 2, 0): return - result = self.sg.text_search("%s Text Search" % self._prefix, - {"Shot": [], "Asset": []}) + result = self.sg.text_search( + "%s Text Search" % self._prefix, {"Shot": [], "Asset": []} + ) matches = result["matches"] @@ -2750,12 +3171,15 @@ def test_complex_entity_filter(self): if not self.sg.server_caps.version or self.sg.server_caps.version < (6, 2, 0): return - result = self.sg.text_search("%s Text Search" % self._prefix, - { - "Shot": [["code", "ends_with", "3"]], - "Asset": [{"filter_operator": "any", - "filters": [["code", "ends_with", "4"]]}] - }) + result = self.sg.text_search( + "%s Text Search" % self._prefix, + { + "Shot": [["code", "ends_with", "3"]], + "Asset": [ + {"filter_operator": "any", "filters": [["code", "ends_with", "4"]]} + ], + }, + ) matches = result["matches"] @@ -2775,131 +3199,175 @@ class TestReadAdditionalFilterPresets(base.LiveTestBase): def test_simple_case(self): if self.sg_version < (7, 0, 0): - warnings.warn("Test bypassed because PTR server used does not support this feature.", FutureWarning) + warnings.warn( + "Test bypassed because PTR server used does not support this feature.", + FutureWarning, + ) return - filters = [ - ["project", "is", self.project], - ["id", "is", self.version["id"]] - ] + filters = [["project", "is", self.project], ["id", "is", self.version["id"]]] fields = ["id"] - additional_filters = [{"preset_name": "LATEST", "latest_by": "ENTITIES_CREATED_AT"}] + additional_filters = [ + {"preset_name": "LATEST", "latest_by": "ENTITIES_CREATED_AT"} + ] - versions = self.sg.find("Version", filters, fields=fields, additional_filter_presets=additional_filters) + versions = self.sg.find( + "Version", + filters, + fields=fields, + additional_filter_presets=additional_filters, + ) version = versions[0] self.assertEqual("Version", version["type"]) self.assertEqual(self.version["id"], version["id"]) def test_find_one(self): if self.sg_version < (7, 0, 0): - warnings.warn("Test bypassed because PTR server used does not support this feature.", FutureWarning) + warnings.warn( + "Test bypassed because PTR server used does not support this feature.", + FutureWarning, + ) return - filters = [ - ["project", "is", self.project], - ["id", "is", self.version["id"]] - ] + filters = [["project", "is", self.project], ["id", "is", self.version["id"]]] fields = ["id"] - additional_filters = [{"preset_name": "LATEST", "latest_by": "ENTITIES_CREATED_AT"}] + additional_filters = [ + {"preset_name": "LATEST", "latest_by": "ENTITIES_CREATED_AT"} + ] - version = self.sg.find_one("Version", filters, fields=fields, additional_filter_presets=additional_filters) + version = self.sg.find_one( + "Version", + filters, + fields=fields, + additional_filter_presets=additional_filters, + ) self.assertEqual("Version", version["type"]) self.assertEqual(self.version["id"], version["id"]) def test_filter_with_no_name(self): if self.sg_version < (7, 0, 0): - warnings.warn("Test bypassed because PTR server used does not support this feature.", FutureWarning) + warnings.warn( + "Test bypassed because PTR server used does not support this feature.", + FutureWarning, + ) return - filters = [ - ["project", "is", self.project], - ["id", "is", self.version["id"]] - ] + filters = [["project", "is", self.project], ["id", "is", self.version["id"]]] fields = ["id"] additional_filters = [{}] - self.assertRaises(shotgun_api3.Fault, - self.sg.find, - "Version", filters, fields=fields, additional_filter_presets=additional_filters) + self.assertRaises( + shotgun_api3.Fault, + self.sg.find, + "Version", + filters, + fields=fields, + additional_filter_presets=additional_filters, + ) def test_invalid_filter(self): if self.sg_version < (7, 0, 0): - warnings.warn("Test bypassed because PTR server used does not support this feature.", FutureWarning) + warnings.warn( + "Test bypassed because PTR server used does not support this feature.", + FutureWarning, + ) return - filters = [ - ["project", "is", self.project], - ["id", "is", self.version["id"]] - ] + filters = [["project", "is", self.project], ["id", "is", self.version["id"]]] fields = ["id"] additional_filters = [{"preset_name": "BAD_FILTER"}] - self.assertRaises(shotgun_api3.Fault, - self.sg.find, - "Version", filters, fields=fields, additional_filter_presets=additional_filters) + self.assertRaises( + shotgun_api3.Fault, + self.sg.find, + "Version", + filters, + fields=fields, + additional_filter_presets=additional_filters, + ) def test_filter_not_iterable(self): if self.sg_version < (7, 0, 0): - warnings.warn("Test bypassed because PTR server used does not support this feature.", FutureWarning) + warnings.warn( + "Test bypassed because PTR server used does not support this feature.", + FutureWarning, + ) return - filters = [ - ["project", "is", self.project], - ["id", "is", self.version["id"]] - ] + filters = [["project", "is", self.project], ["id", "is", self.version["id"]]] fields = ["id"] additional_filters = 3 - self.assertRaises(shotgun_api3.Fault, - self.sg.find, - "Version", filters, fields=fields, additional_filter_presets=additional_filters) + self.assertRaises( + shotgun_api3.Fault, + self.sg.find, + "Version", + filters, + fields=fields, + additional_filter_presets=additional_filters, + ) def test_filter_not_list_of_iterable(self): if self.sg_version < (7, 0, 0): - warnings.warn("Test bypassed because PTR server used does not support this feature.", FutureWarning) + warnings.warn( + "Test bypassed because PTR server used does not support this feature.", + FutureWarning, + ) return - filters = [ - ["project", "is", self.project], - ["id", "is", self.version["id"]] - ] + filters = [["project", "is", self.project], ["id", "is", self.version["id"]]] fields = ["id"] additional_filters = [3] - self.assertRaises(shotgun_api3.Fault, - self.sg.find, - "Version", filters, fields=fields, additional_filter_presets=additional_filters) + self.assertRaises( + shotgun_api3.Fault, + self.sg.find, + "Version", + filters, + fields=fields, + additional_filter_presets=additional_filters, + ) def test_multiple_latest_filters(self): if self.sg_version < (7, 0, 0): - warnings.warn("Test bypassed because PTR server used does not support this feature.", FutureWarning) + warnings.warn( + "Test bypassed because PTR server used does not support this feature.", + FutureWarning, + ) return - filters = [ - ["project", "is", self.project], - ["id", "is", self.version["id"]] - ] + filters = [["project", "is", self.project], ["id", "is", self.version["id"]]] fields = ["id"] - additional_filters = ({"preset_name": "LATEST", "latest_by": "ENTITY_CREATED_AT"}, - {"preset_name": "LATEST", "latest_by": "PIPELINE_STEP_NUMBER_AND_ENTITIES_CREATED_AT"}) + additional_filters = ( + {"preset_name": "LATEST", "latest_by": "ENTITY_CREATED_AT"}, + { + "preset_name": "LATEST", + "latest_by": "PIPELINE_STEP_NUMBER_AND_ENTITIES_CREATED_AT", + }, + ) - self.assertRaises(shotgun_api3.Fault, - self.sg.find, - "Version", filters, fields=fields, additional_filter_presets=additional_filters) + self.assertRaises( + shotgun_api3.Fault, + self.sg.find, + "Version", + filters, + fields=fields, + additional_filter_presets=additional_filters, + ) def test_modify_visibility(self): """ @@ -2908,7 +3376,10 @@ def test_modify_visibility(self): # If the version of Shotgun is too old, do not run this test. # TODO: Update this with the real version number once the feature is released. if self.sg_version < (8, 5, 0): - warnings.warn("Test bypassed because PTR server used does not support this feature.", FutureWarning) + warnings.warn( + "Test bypassed because PTR server used does not support this feature.", + FutureWarning, + ) return field_display_name = "Project Visibility Test" @@ -2920,7 +3391,9 @@ def test_modify_visibility(self): self.sg.schema_field_create("Asset", "text", "Project Visibility Test") # Grab any two projects that we can use for toggling the visible property with. - projects = self.sg.find("Project", [], order=[{"field_name": "id", "direction": "asc"}]) + projects = self.sg.find( + "Project", [], order=[{"field_name": "id", "direction": "asc"}] + ) project_1 = projects[0] project_2 = projects[1] @@ -2929,21 +3402,27 @@ def test_modify_visibility(self): self.sg.schema_field_update("Asset", field_name, {"visible": True}, project_1) self.assertEqual( {"value": True, "editable": True}, - self.sg.schema_field_read("Asset", field_name, project_1)[field_name]["visible"] + self.sg.schema_field_read("Asset", field_name, project_1)[field_name][ + "visible" + ], ) self.sg.schema_field_update("Asset", field_name, {"visible": True}, project_2) self.assertEqual( {"value": True, "editable": True}, - self.sg.schema_field_read("Asset", field_name, project_2)[field_name]["visible"] + self.sg.schema_field_read("Asset", field_name, project_2)[field_name][ + "visible" + ], ) # Built-in fields should remain not editable. - self.assertFalse(self.sg.schema_field_read("Asset", "code")["code"]["visible"]["editable"]) + self.assertFalse( + self.sg.schema_field_read("Asset", "code")["code"]["visible"]["editable"] + ) # Custom fields should be editable self.assertEqual( {"value": True, "editable": True}, - self.sg.schema_field_read("Asset", field_name)[field_name]["visible"] + self.sg.schema_field_read("Asset", field_name)[field_name]["visible"], ) # Hide the field on project 1 @@ -2951,20 +3430,26 @@ def test_modify_visibility(self): # It should not be visible anymore. self.assertEqual( {"value": False, "editable": True}, - self.sg.schema_field_read("Asset", field_name, project_1)[field_name]["visible"] + self.sg.schema_field_read("Asset", field_name, project_1)[field_name][ + "visible" + ], ) # The field should be visible on the second project. self.assertEqual( {"value": True, "editable": True}, - self.sg.schema_field_read("Asset", field_name, project_2)[field_name]["visible"] + self.sg.schema_field_read("Asset", field_name, project_2)[field_name][ + "visible" + ], ) # Restore the visibility on the field. self.sg.schema_field_update("Asset", field_name, {"visible": True}, project_1) self.assertEqual( {"value": True, "editable": True}, - self.sg.schema_field_read("Asset", field_name, project_1)[field_name]["visible"] + self.sg.schema_field_read("Asset", field_name, project_1)[field_name][ + "visible" + ], ) @@ -2983,6 +3468,7 @@ def test_import_httplib(self): proxied to allow this. """ from shotgun_api3.lib import httplib2 + # Ensure that Http object is available. This is a good indication that # the httplib2 module contents are importable. self.assertTrue(hasattr(httplib2, "Http")) @@ -3003,6 +3489,7 @@ def test_import_httplib(self): # import -- this is a good indication that external httplib2 imports # from shotgun_api3 will work as expected. from shotgun_api3.lib.httplib2 import socks + self.assertTrue(isinstance(socks, types.ModuleType)) # Make sure that objects in socks are available as expected self.assertTrue(hasattr(socks, "HTTPError")) @@ -3025,7 +3512,7 @@ def _get_path(url): """ # url_parse returns native objects for older python versions (2.4) if isinstance(url, dict): - return url.get('path') + return url.get("path") elif isinstance(url, tuple): # 3rd component is the path return url[2] @@ -3033,5 +3520,5 @@ def _get_path(url): return url.path -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/test_client.py b/tests/test_client.py index dc3fa3ec5..e29c6158d 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -18,6 +18,7 @@ from shotgun_api3.lib.six.moves import urllib from shotgun_api3.lib import six, sgutils + try: import simplejson as json except ImportError: @@ -48,12 +49,12 @@ def b64encode(val): class TestShotgunClient(base.MockTestBase): - '''Test case for shotgun api with server interactions mocked.''' + """Test case for shotgun api with server interactions mocked.""" def setUp(self): super(TestShotgunClient, self).setUp() # get domain and uri scheme - match = re.search('(https?://)(.*)', self.server_url) + match = re.search("(https?://)(.*)", self.server_url) self.uri_prefix = match.group(1) self.domain = match.group(2) # always want the mock on @@ -75,8 +76,8 @@ def test_detect_client_caps(self): # todo test for version string (eg. "1.2.3ng") or "unknown" def test_detect_server_caps(self): - '''test_detect_server_caps tests that ServerCapabilities object is made - with appropriate settings for given server version.''' + """test_detect_server_caps tests that ServerCapabilities object is made + with appropriate settings for given server version.""" # has paging is tested else where. server_info = {"version": [9, 9, 9]} self._mock_http(server_info) @@ -94,12 +95,14 @@ def test_detect_server_caps(self): self.assertTrue(self.sg.server_caps.is_dev) def test_server_version_json(self): - '''test_server_version_json tests expected versions for json support.''' + """test_server_version_json tests expected versions for json support.""" sc = ServerCapabilities("foo", {"version": (2, 4, 0)}) sc.version = (2, 3, 99) self.assertRaises(api.ShotgunError, sc._ensure_json_supported) - self.assertRaises(api.ShotgunError, ServerCapabilities, "foo", {"version": (2, 2, 0)}) + self.assertRaises( + api.ShotgunError, ServerCapabilities, "foo", {"version": (2, 2, 0)} + ) sc.version = (0, 0, 0) self.assertRaises(api.ShotgunError, sc._ensure_json_supported) @@ -146,18 +149,20 @@ def auth_args(): self.assertRaises(api.Fault, self.sg.delete, "FakeType", 1) self.assertTrue("session_uuid" not in auth_args()) - my_uuid = '5a1d49b0-0c69-11e0-a24c-003048d17544' + my_uuid = "5a1d49b0-0c69-11e0-a24c-003048d17544" self.sg.set_session_uuid(my_uuid) self.assertRaises(api.Fault, self.sg.delete, "FakeType", 1) self.assertEqual(my_uuid, auth_args()["session_uuid"]) def test_url(self): """Server url is parsed correctly""" - login = self.human_user['login'] + login = self.human_user["login"] password = self.human_password self.assertRaises(ValueError, api.Shotgun, None, None, None, connect=False) - self.assertRaises(ValueError, api.Shotgun, "file://foo.com", None, None, connect=False) + self.assertRaises( + ValueError, api.Shotgun, "file://foo.com", None, None, connect=False + ) self.assertEqual("/api3/json", self.sg.config.api_path) @@ -174,7 +179,7 @@ def test_b64encode(self): login = "thelogin" password = "%thepassw0r#$" login_password = "%s:%s" % (login, password) - expected = 'dGhlbG9naW46JXRoZXBhc3N3MHIjJA==' + expected = "dGhlbG9naW46JXRoZXBhc3N3MHIjJA==" result = b64encode(urllib.parse.unquote(login_password)).strip() self.assertEqual(expected, result) @@ -192,8 +197,7 @@ def test_read_config(self): def test_split_url(self): """Validate that url parts are properly extracted.""" - sg = api.Shotgun("https://ci.shotgunstudio.com", - "foo", "bar", connect=False) + sg = api.Shotgun("https://ci.shotgunstudio.com", "foo", "bar", connect=False) base_url = "https://ci.shotgunstudio.com" expected_server = "ci.shotgunstudio.com" @@ -225,7 +229,7 @@ def test_split_url(self): def test_authorization(self): """Authorization passed to server""" - login = self.human_user['login'] + login = self.human_user["login"] password = self.human_password login_password = "%s:%s" % (login, password) # login:password@domain @@ -233,7 +237,7 @@ def test_authorization(self): self.sg = api.Shotgun(auth_url, "foo", "bar", connect=False) self._setup_mock() - self._mock_http({'version': [2, 4, 0, u'Dev']}) + self._mock_http({"version": [2, 4, 0, "Dev"]}) self.sg.info() @@ -279,7 +283,7 @@ def test_user_agent(self): client_caps.py_version, client_caps.platform.capitalize(), client_caps.ssl_version, - ssl_validate_lut[config.no_ssl_validation] + ssl_validate_lut[config.no_ssl_validation], ) self.assertEqual(expected, headers.get("user-agent")) @@ -293,7 +297,7 @@ def test_user_agent(self): client_caps.py_version, client_caps.platform.capitalize(), client_caps.ssl_version, - ssl_validate_lut[config.no_ssl_validation] + ssl_validate_lut[config.no_ssl_validation], ) self.assertEqual(expected, headers.get("user-agent")) @@ -307,7 +311,7 @@ def test_user_agent(self): client_caps.py_version, client_caps.platform.capitalize(), client_caps.ssl_version, - ssl_validate_lut[config.no_ssl_validation] + ssl_validate_lut[config.no_ssl_validation], ) self.assertEqual(expected, headers.get("user-agent")) @@ -327,14 +331,15 @@ def test_network_retry(self): self.assertRaises(httplib2.HttpLib2Error, self.sg.info) self.assertTrue( self.sg.config.max_rpc_attempts == self.sg._http_request.call_count, - "Call is repeated") + "Call is repeated", + ) # Ensure that sleep was called with the retry interval between each attempt attempt_interval = self.sg.config.rpc_attempt_interval / 1000.0 calls = [mock.callargs(((attempt_interval,), {}))] - calls *= (self.sg.config.max_rpc_attempts - 1) + calls *= self.sg.config.max_rpc_attempts - 1 self.assertTrue( mock_sleep.call_args_list == calls, - "Call is repeated at correct interval." + "Call is repeated at correct interval.", ) def test_set_retry_interval(self): @@ -342,12 +347,15 @@ def test_set_retry_interval(self): original_env_val = os.environ.pop("SHOTGUN_API_RETRY_INTERVAL", None) try: + def run_interval_test(expected_interval, interval_property=None): - self.sg = api.Shotgun(self.config.server_url, - self.config.script_name, - self.config.api_key, - http_proxy=self.config.http_proxy, - connect=self.connect) + self.sg = api.Shotgun( + self.config.server_url, + self.config.script_name, + self.config.api_key, + http_proxy=self.config.http_proxy, + connect=self.connect, + ) self._setup_mock() if interval_property: # if a value was provided for interval_property, set the @@ -424,7 +432,10 @@ def test_call_rpc(self): # Test unicode mixed with utf-8 as reported in Ticket #17959 d = {"results": ["foo", "bar"]} - a = {"utf_str": "\xe2\x88\x9a", "unicode_str": sgutils.ensure_text("\xe2\x88\x9a")} + a = { + "utf_str": "\xe2\x88\x9a", + "unicode_str": sgutils.ensure_text("\xe2\x88\x9a"), + } self._mock_http(d) rv = self.sg._call_rpc("list", a) expected = "rpc response with list result" @@ -460,11 +471,14 @@ def test_upload_s3_503(self): """ this_dir, _ = os.path.split(__file__) storage_url = "http://foo.com/" - path = os.path.abspath(os.path.expanduser( - os.path.join(this_dir, "sg_logo.jpg"))) + path = os.path.abspath( + os.path.expanduser(os.path.join(this_dir, "sg_logo.jpg")) + ) # Expected HTTPError exception error message - expected = "The server is currently down or to busy to reply." \ - "Please try again later." + expected = ( + "The server is currently down or to busy to reply." + "Please try again later." + ) # Test the Internal function that is used to upload each # data part in the context of multi-part uploads to S3, we @@ -474,8 +488,9 @@ def test_upload_s3_503(self): # Test the max retries attempt self.assertTrue( self.sg.MAX_ATTEMPTS == self.sg._make_upload_request.call_count, - f"Call is repeated up to {self.sg.MAX_ATTEMPTS} times") - + f"Call is repeated up to {self.sg.MAX_ATTEMPTS} times", + ) + def test_upload_s3_500(self): """ Test 500 response is retried when uploading to S3. @@ -483,11 +498,14 @@ def test_upload_s3_500(self): self._setup_mock(s3_status_code_error=500) this_dir, _ = os.path.split(__file__) storage_url = "http://foo.com/" - path = os.path.abspath(os.path.expanduser( - os.path.join(this_dir, "sg_logo.jpg"))) + path = os.path.abspath( + os.path.expanduser(os.path.join(this_dir, "sg_logo.jpg")) + ) # Expected HTTPError exception error message - expected = "The server is currently down or to busy to reply." \ - "Please try again later." + expected = ( + "The server is currently down or to busy to reply." + "Please try again later." + ) # Test the Internal function that is used to upload each # data part in the context of multi-part uploads to S3, we @@ -497,8 +515,9 @@ def test_upload_s3_500(self): # Test the max retries attempt self.assertTrue( self.sg.MAX_ATTEMPTS == self.sg._make_upload_request.call_count, - f"Call is repeated up to {self.sg.MAX_ATTEMPTS} times") - + f"Call is repeated up to {self.sg.MAX_ATTEMPTS} times", + ) + def test_upload_s3_urlerror__get_attachment_upload_info(self): """ Test URLError response is retried when invoking _send_form @@ -520,7 +539,7 @@ def test_upload_s3_urlerror__get_attachment_upload_info(self): self.assertEqual( self.sg.MAX_ATTEMPTS, mock_opener.return_value.open.call_count, - f"Call is repeated up to {self.sg.MAX_ATTEMPTS} times" + f"Call is repeated up to {self.sg.MAX_ATTEMPTS} times", ) # Test the exception message @@ -554,7 +573,7 @@ def test_upload_s3_urlerror__upload_to_storage(self): self.assertEqual( self.sg.MAX_ATTEMPTS, self.sg._make_upload_request.call_count, - f"Call is repeated up to {self.sg.MAX_ATTEMPTS} times" + f"Call is repeated up to {self.sg.MAX_ATTEMPTS} times", ) # Test the exception message @@ -566,19 +585,15 @@ def test_transform_data(self): timestamp = time.time() # microseconds will be last during transforms now = datetime.datetime.fromtimestamp(timestamp).replace( - microsecond=0, tzinfo=SG_TIMEZONE.local) - utc_now = datetime.datetime.utcfromtimestamp(timestamp).replace( - microsecond=0) - local = { - "date": now.strftime('%Y-%m-%d'), - "datetime": now, - "time": now.time() - } + microsecond=0, tzinfo=SG_TIMEZONE.local + ) + utc_now = datetime.datetime.utcfromtimestamp(timestamp).replace(microsecond=0) + local = {"date": now.strftime("%Y-%m-%d"), "datetime": now, "time": now.time()} # date will still be the local date, because they are not transformed utc = { - "date": now.strftime('%Y-%m-%d'), + "date": now.strftime("%Y-%m-%d"), "datetime": utc_now, - "time": utc_now.time() + "time": utc_now.time(), } def _datetime(s, f): @@ -587,7 +602,7 @@ def _datetime(s, f): def assert_wire(wire, match): self.assertTrue(isinstance(wire["date"], str)) d = _datetime(wire["date"], "%Y-%m-%d").date() - d = wire['date'] + d = wire["date"] self.assertEqual(match["date"], d) self.assertTrue(isinstance(wire["datetime"], str)) d = _datetime(wire["datetime"], "%Y-%m-%dT%H:%M:%SZ") @@ -619,33 +634,35 @@ def assert_wire(wire, match): def test_encode_payload(self): """Request body is encoded as JSON""" - d = {"this is ": u"my data \u00E0"} + d = {"this is ": "my data \u00e0"} j = self.sg._encode_payload(d) self.assertTrue(isinstance(j, bytes)) - d = { - "this is ": u"my data" - } + d = {"this is ": "my data"} j = self.sg._encode_payload(d) self.assertTrue(isinstance(j, bytes)) def test_decode_response_ascii(self): - self._assert_decode_resonse(True, sgutils.ensure_str(u"my data \u00E0", encoding='utf8')) + self._assert_decode_resonse( + True, sgutils.ensure_str("my data \u00e0", encoding="utf8") + ) def test_decode_response_unicode(self): - self._assert_decode_resonse(False, u"my data \u00E0") + self._assert_decode_resonse(False, "my data \u00e0") def _assert_decode_resonse(self, ensure_ascii, data): """HTTP Response is decoded as JSON or text""" headers = {"content-type": "application/json;charset=utf-8"} d = {"this is ": data} - sg = api.Shotgun(self.config.server_url, - self.config.script_name, - self.config.api_key, - http_proxy=self.config.http_proxy, - ensure_ascii=ensure_ascii, - connect=False) + sg = api.Shotgun( + self.config.server_url, + self.config.script_name, + self.config.api_key, + http_proxy=self.config.http_proxy, + ensure_ascii=ensure_ascii, + connect=False, + ) if six.PY3: j = json.dumps(d, ensure_ascii=ensure_ascii) @@ -663,11 +680,11 @@ def test_parse_records(self): """Parse records to replace thumbnail and local paths""" system = platform.system().lower() - if system == 'darwin': + if system == "darwin": local_path_field = "local_path_mac" - elif system in ['windows', 'microsoft']: + elif system in ["windows", "microsoft"]: local_path_field = "local_path_windows" - elif system == 'linux': + elif system == "linux": local_path_field = "local_path_linux" orig = { "type": "FakeAsset", @@ -676,11 +693,10 @@ def test_parse_records(self): "foo": { "link_type": "local", local_path_field: "/foo/bar.jpg", - } + }, } url = "http://foo/files/0000/0000/0012/232/shot_thumb.jpg" - self.sg._build_thumb_url = mock.Mock( - return_value=url) + self.sg._build_thumb_url = mock.Mock(return_value=url) modified, txt = self.sg._parse_records([orig, "plain text"]) self.assertEqual("plain text", txt, "non dict value is left as is") @@ -703,14 +719,15 @@ def test_thumb_url(self): url = self.sg._build_thumb_url("FakeAsset", 1234) - self.assertEqual( - "http://foo.com/files/0000/0000/0012/232/shot_thumb.jpg", url) + self.assertEqual("http://foo.com/files/0000/0000/0012/232/shot_thumb.jpg", url) self.assertTrue(self.sg._http_request.called, "http request made to get url") args, _ = self.sg._http_request.call_args verb, path, body, headers = args self.assertEqual( "/upload/get_thumbnail_url?entity_type=FakeAsset&entity_id=1234", - path, "thumbnail url called with correct args") + path, + "thumbnail url called with correct args", + ) resp = "0\nSome Error" self._mock_http(resp, headers={"content-type": "text/plain"}) @@ -722,27 +739,34 @@ def test_thumb_url(self): class TestShotgunClientInterface(base.MockTestBase): - '''Tests expected interface for shotgun module and client''' + """Tests expected interface for shotgun module and client""" def test_client_interface(self): - expected_attributes = ['base_url', - 'config', - 'client_caps', - 'server_caps'] + expected_attributes = ["base_url", "config", "client_caps", "server_caps"] for expected_attribute in expected_attributes: if not hasattr(self.sg, expected_attribute): - assert False, '%s not found on %s' % (expected_attribute, - self.sg) + assert False, "%s not found on %s" % (expected_attribute, self.sg) def test_module_interface(self): import shotgun_api3 - expected_contents = ['Shotgun', 'ShotgunError', 'Fault', - 'ProtocolError', 'ResponseError', 'Error', - 'sg_timezone', '__version__'] + + expected_contents = [ + "Shotgun", + "ShotgunError", + "Fault", + "ProtocolError", + "ResponseError", + "Error", + "sg_timezone", + "__version__", + ] for expected_content in expected_contents: if not hasattr(shotgun_api3, expected_content): - assert False, '%s not found on module %s' % (expected_content, shotgun_api3) + assert False, "%s not found on module %s" % ( + expected_content, + shotgun_api3, + ) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/test_config_file b/tests/test_config_file index 8215eecde..d642f96c2 100644 --- a/tests/test_config_file +++ b/tests/test_config_file @@ -4,4 +4,4 @@ script_name : xyz api_key : %%abce [TEST_DATA] -project_name : hjkl \ No newline at end of file +project_name : hjkl diff --git a/tests/test_mockgun.py b/tests/test_mockgun.py index 84e5cb2e7..1395355fa 100644 --- a/tests/test_mockgun.py +++ b/tests/test_mockgun.py @@ -42,14 +42,11 @@ from shotgun_api3 import ShotgunError -mockgun_schema_folder = os.path.join( - os.path.dirname(__file__), - "mockgun" -) +mockgun_schema_folder = os.path.join(os.path.dirname(__file__), "mockgun") Mockgun.set_schema_paths( os.path.join(mockgun_schema_folder, "schema.pickle"), - os.path.join(mockgun_schema_folder, "schema_entity.pickle") + os.path.join(mockgun_schema_folder, "schema_entity.pickle"), ) @@ -64,6 +61,7 @@ def test_interface_intact(self): """ from shotgun_api3.lib import mockgun + # Try to access everything. If something is missing, it will raise an # error. mockgun.MockgunError @@ -82,7 +80,9 @@ def setUp(self): """ super(TestValidateFilterSyntax, self).setUp() - self._mockgun = Mockgun("https://test.shotgunstudio.com", login="user", password="1234") + self._mockgun = Mockgun( + "https://test.shotgunstudio.com", login="user", password="1234" + ) self._mockgun.create("Shot", {"code": "shot"}) @@ -94,24 +94,16 @@ def test_filter_array_or_dict(self): self._mockgun.find( "Shot", [ - { - "filter_operator": "any", - "filters": [["code", "is", "shot"]] - }, - [ - "code", "is", "shot" - ] - ] + {"filter_operator": "any", "filters": [["code", "is", "shot"]]}, + ["code", "is", "shot"], + ], ) # We can't have not dict/list values for filters however. self.assertRaisesRegex( ShotgunError, "Filters can only be lists or dictionaries, not int.", - lambda: self._mockgun.find( - "Shot", - [1] - ) + lambda: self._mockgun.find("Shot", [1]), ) @@ -124,14 +116,21 @@ def setUp(self): """ Creates test data. """ - self._mockgun = Mockgun("https://test.shotgunstudio.com", login="user", password="1234") + self._mockgun = Mockgun( + "https://test.shotgunstudio.com", login="user", password="1234" + ) - self._project_link = self._mockgun.create("Project", {"name": "project", "archived": False}) + self._project_link = self._mockgun.create( + "Project", {"name": "project", "archived": False} + ) # This entity will ensure that a populated link field will be comparable. self._mockgun.create( "PipelineConfiguration", - {"code": "with_project", "project": self._project_link, } + { + "code": "with_project", + "project": self._project_link, + }, ) # This entity will ensure that an unpopulated link field will be comparable. @@ -145,17 +144,23 @@ def test_searching_for_none_entity_field(self): items = self._mockgun.find("PipelineConfiguration", [["project", "is", None]]) self.assertEqual(len(items), 1) - items = self._mockgun.find("PipelineConfiguration", [["project", "is_not", None]]) + items = self._mockgun.find( + "PipelineConfiguration", [["project", "is_not", None]] + ) self.assertEqual(len(items), 1) def test_searching_for_initialized_entity_field(self): """ Ensures that comparison with an entity works. """ - items = self._mockgun.find("PipelineConfiguration", [["project", "is", self._project_link]]) + items = self._mockgun.find( + "PipelineConfiguration", [["project", "is", self._project_link]] + ) self.assertEqual(len(items), 1) - items = self._mockgun.find("PipelineConfiguration", [["project", "is_not", self._project_link]]) + items = self._mockgun.find( + "PipelineConfiguration", [["project", "is_not", self._project_link]] + ) self.assertEqual(len(items), 1) def test_find_entity_with_none_link(self): @@ -164,7 +169,9 @@ def test_find_entity_with_none_link(self): """ # The pipeline configuration without_project doesn't have the project field set, so we're expecting # it to not be returned here. - items = self._mockgun.find("PipelineConfiguration", [["project.Project.archived", "is", False]]) + items = self._mockgun.find( + "PipelineConfiguration", [["project.Project.archived", "is", False]] + ) self.assertEqual(len(items), 1) self.assertEqual(items[0]["id"], self._project_link["id"]) @@ -173,11 +180,14 @@ class TestTextFieldOperators(unittest.TestCase): """ Checks if text field comparison work. """ + def setUp(self): """ Creates test data. """ - self._mockgun = Mockgun("https://test.shotgunstudio.com", login="user", password="1234") + self._mockgun = Mockgun( + "https://test.shotgunstudio.com", login="user", password="1234" + ) self._user = self._mockgun.create("HumanUser", {"login": "user"}) def test_operator_contains(self): @@ -198,7 +208,9 @@ def setUp(self): Creates test data. """ - self._mockgun = Mockgun("https://test.shotgunstudio.com", login="user", password="1234") + self._mockgun = Mockgun( + "https://test.shotgunstudio.com", login="user", password="1234" + ) # Create two users to assign to the pipeline configurations. self._user1 = self._mockgun.create("HumanUser", {"login": "user1"}) @@ -206,54 +218,67 @@ def setUp(self): # Create pipeline configurations that are assigned none, one or two users. self._mockgun.create( - "PipelineConfiguration", - {"code": "with_user1", "users": [self._user1]} + "PipelineConfiguration", {"code": "with_user1", "users": [self._user1]} ) self._mockgun.create( - "PipelineConfiguration", - {"code": "with_user2", "users": [self._user2]} + "PipelineConfiguration", {"code": "with_user2", "users": [self._user2]} ) self._mockgun.create( "PipelineConfiguration", - {"code": "with_both", "users": [self._user2, self._user1]} + {"code": "with_both", "users": [self._user2, self._user1]}, ) self._mockgun.create( - "PipelineConfiguration", - {"code": "with_none", "users": []} + "PipelineConfiguration", {"code": "with_none", "users": []} ) def test_find_by_sub_entity_field(self): """ Ensures that queries on linked entity fields works. """ - items = self._mockgun.find("PipelineConfiguration", [["users.HumanUser.login", "is", "user1"]]) + items = self._mockgun.find( + "PipelineConfiguration", [["users.HumanUser.login", "is", "user1"]] + ) self.assertEqual(len(items), 2) - items = self._mockgun.find("PipelineConfiguration", [["users.HumanUser.login", "is", "user2"]]) + items = self._mockgun.find( + "PipelineConfiguration", [["users.HumanUser.login", "is", "user2"]] + ) self.assertEqual(len(items), 2) - items = self._mockgun.find("PipelineConfiguration", [["users.HumanUser.login", "contains", "ser"]]) + items = self._mockgun.find( + "PipelineConfiguration", [["users.HumanUser.login", "contains", "ser"]] + ) self.assertEqual(len(items), 3) # Lets get fancy a bit. - items = self._mockgun.find("PipelineConfiguration", [{ - "filter_operator": "any", - "filters": [ - ["users.HumanUser.login", "is", "user1"], - ["users.HumanUser.login", "is", "user2"] - ]}] + items = self._mockgun.find( + "PipelineConfiguration", + [ + { + "filter_operator": "any", + "filters": [ + ["users.HumanUser.login", "is", "user1"], + ["users.HumanUser.login", "is", "user2"], + ], + } + ], ) self.assertEqual(len(items), 3) - items = self._mockgun.find("PipelineConfiguration", [{ - "filter_operator": "all", - "filters": [ - ["users.HumanUser.login", "is", "user1"], - ["users.HumanUser.login", "is", "user2"] - ]}] + items = self._mockgun.find( + "PipelineConfiguration", + [ + { + "filter_operator": "all", + "filters": [ + ["users.HumanUser.login", "is", "user1"], + ["users.HumanUser.login", "is", "user2"], + ], + } + ], ) self.assertEqual(len(items), 1) @@ -261,16 +286,20 @@ def test_find_with_none(self): """ Ensures comparison with multi-entity fields and None works. """ - items = self._mockgun.find("PipelineConfiguration", [["users", "is", None]], ["users"]) + items = self._mockgun.find( + "PipelineConfiguration", [["users", "is", None]], ["users"] + ) self.assertEqual(len(items), 1) self.assertEqual(items[0]["users"], []) - items = self._mockgun.find("PipelineConfiguration", [["users", "is_not", None]], ["users"]) + items = self._mockgun.find( + "PipelineConfiguration", [["users", "is_not", None]], ["users"] + ) self.assertEqual(len(items), 3) for item in items: self.assertTrue(len(item["users"]) > 0) - + class TestMultiEntityFieldUpdate(unittest.TestCase): """ Ensures multi entity field update modes work. @@ -281,13 +310,15 @@ def setUp(self): Creates test data. """ - self._mockgun = Mockgun("https://test.shotgunstudio.com", login="user", password="1234") + self._mockgun = Mockgun( + "https://test.shotgunstudio.com", login="user", password="1234" + ) # Create two versions to assign to the shot. self._version1 = self._mockgun.create("Version", {"code": "version1"}) self._version2 = self._mockgun.create("Version", {"code": "version2"}) self._version3 = self._mockgun.create("Version", {"code": "version3"}) - + # remove 'code' field for later comparisons del self._version1["code"] del self._version2["code"] @@ -296,15 +327,18 @@ def setUp(self): # Create playlists self._add_playlist = self._mockgun.create( "Playlist", - {"code": "playlist1", "versions": [self._version1, self._version2]} + {"code": "playlist1", "versions": [self._version1, self._version2]}, ) self._remove_playlist = self._mockgun.create( "Playlist", - {"code": "playlist1", "versions": [self._version1, self._version2, self._version3]} + { + "code": "playlist1", + "versions": [self._version1, self._version2, self._version3], + }, ) self._set_playlist = self._mockgun.create( "Playlist", - {"code": "playlist1", "versions": [self._version1, self._version2]} + {"code": "playlist1", "versions": [self._version1, self._version2]}, ) def test_update_add(self): @@ -312,8 +346,10 @@ def test_update_add(self): Ensures that "add" multi_entity_update_mode works. """ self._mockgun.update( - "Playlist", self._add_playlist["id"], {"versions": [self._version3]}, - multi_entity_update_modes={"versions": "add"} + "Playlist", + self._add_playlist["id"], + {"versions": [self._version3]}, + multi_entity_update_modes={"versions": "add"}, ) playlist = self._mockgun.find_one( @@ -328,8 +364,10 @@ def test_update_remove(self): Ensures that "remove" multi_entity_update_mode works. """ self._mockgun.update( - "Playlist", self._remove_playlist["id"], {"versions": [self._version2]}, - multi_entity_update_modes={"versions": "remove"} + "Playlist", + self._remove_playlist["id"], + {"versions": [self._version2]}, + multi_entity_update_modes={"versions": "remove"}, ) playlist = self._mockgun.find_one( @@ -345,14 +383,14 @@ def test_update_set(self): "Playlist", self._set_playlist["id"], {"versions": [self._version2, self._version3]}, - multi_entity_update_modes={"versions": "set"} + multi_entity_update_modes={"versions": "set"}, ) playlist = self._mockgun.find_one( "Playlist", [["id", "is", self._set_playlist["id"]]], ["versions"] ) self.assertEqual(playlist["versions"], [self._version2, self._version3]) - + def test_batch_update(self): self._mockgun.batch( [ @@ -361,7 +399,7 @@ def test_batch_update(self): "entity_type": "Playlist", "entity_id": self._set_playlist["id"], "data": {"versions": [self._version1, self._version2]}, - "multi_entity_update_modes": {"versions": "set"} + "multi_entity_update_modes": {"versions": "set"}, } ] ) @@ -382,44 +420,24 @@ def setUp(self): """ super(TestFilterOperator, self).setUp() - self._mockgun = Mockgun("https://test.shotgunstudio.com", login="user", password="1234") - - self._prj1_link = self._mockgun.create( - "Project", - { - "name": "prj1" - } + self._mockgun = Mockgun( + "https://test.shotgunstudio.com", login="user", password="1234" ) - self._prj2_link = self._mockgun.create( - "Project", - { - "name": "prj2" - } - ) + self._prj1_link = self._mockgun.create("Project", {"name": "prj1"}) + + self._prj2_link = self._mockgun.create("Project", {"name": "prj2"}) self._shot1 = self._mockgun.create( - "Shot", - { - "code": "shot1", - "project": self._prj1_link - } + "Shot", {"code": "shot1", "project": self._prj1_link} ) self._shot2 = self._mockgun.create( - "Shot", - { - "code": "shot2", - "project": self._prj1_link - } + "Shot", {"code": "shot2", "project": self._prj1_link} ) self._shot3 = self._mockgun.create( - "Shot", - { - "code": "shot3", - "project": self._prj2_link - } + "Shot", {"code": "shot3", "project": self._prj2_link} ) def test_simple_filter_operators(self): @@ -428,26 +446,24 @@ def test_simple_filter_operators(self): """ shots = self._mockgun.find( "Shot", - [{ - "filter_operator": "any", - "filters": [ - ["code", "is", "shot1"], - ["code", "is", "shot2"] - ] - }] + [ + { + "filter_operator": "any", + "filters": [["code", "is", "shot1"], ["code", "is", "shot2"]], + } + ], ) self.assertEqual(len(shots), 2) shots = self._mockgun.find( "Shot", - [{ - "filter_operator": "all", - "filters": [ - ["code", "is", "shot1"], - ["code", "is", "shot2"] - ] - }] + [ + { + "filter_operator": "all", + "filters": [["code", "is", "shot1"], ["code", "is", "shot2"]], + } + ], ) self.assertEqual(len(shots), 0) @@ -467,19 +483,19 @@ def test_nested_filter_operators(self): "filter_operator": "all", "filters": [ ["code", "is", "shot1"], - ["project", "is", self._prj1_link] - ] + ["project", "is", self._prj1_link], + ], }, { "filter_operator": "all", "filters": [ ["code", "is", "shot3"], - ["project", "is", self._prj2_link] - ] - } - ] + ["project", "is", self._prj2_link], + ], + }, + ], } - ] + ], ) self.assertEqual(len(shots), 2) @@ -490,24 +506,14 @@ def test_invalid_operator(self): ShotgunError, "Unknown filter_operator type: bad", lambda: self._mockgun.find( - "Shot", - [ - { - "filter_operator": "bad", - "filters": [] - } - ]) + "Shot", [{"filter_operator": "bad", "filters": []}] + ), ) self.assertRaisesRegex( ShotgunError, "Bad filter operator, requires keys 'filter_operator' and 'filters',", - lambda: self._mockgun.find( - "Shot", - [ - { - } - ]) + lambda: self._mockgun.find("Shot", [{}]), ) @@ -535,5 +541,5 @@ def test_set_server_params_with_url_with_path(self): self.assertEqual(mockgun.config.api_path, "/something/api3/json") -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/tests/test_proxy.py b/tests/test_proxy.py index 85f70500a..cb713cd9d 100644 --- a/tests/test_proxy.py +++ b/tests/test_proxy.py @@ -15,22 +15,24 @@ class ServerConnectionTest(base.TestBase): - '''Tests for server connection''' + """Tests for server connection""" + def setUp(self): super(ServerConnectionTest, self).setUp() def test_connection(self): - '''Tests server connects and returns nothing''' + """Tests server connects and returns nothing""" result = self.sg.connect() self.assertEqual(result, None) def test_proxy_info(self): - '''check proxy value depending http_proxy setting in config''' + """check proxy value depending http_proxy setting in config""" self.sg.connect() if self.config.http_proxy: sys.stderr.write("[WITH PROXY] ") - self.assertTrue(isinstance(self.sg._connection.proxy_info, - api.lib.httplib2.ProxyInfo)) + self.assertTrue( + isinstance(self.sg._connection.proxy_info, api.lib.httplib2.ProxyInfo) + ) else: sys.stderr.write("[NO PROXY] ") self.assertEqual(self.sg._connection.proxy_info, None) diff --git a/tests/test_unit.py b/tests/test_unit.py index 84304cab7..de996c553 100644 --- a/tests/test_unit.py +++ b/tests/test_unit.py @@ -21,30 +21,35 @@ class TestShotgunInit(unittest.TestCase): - '''Test case for Shotgun.__init__''' + """Test case for Shotgun.__init__""" + def setUp(self): - self.server_path = 'http://server_path' - self.script_name = 'script_name' - self.api_key = 'api_key' + self.server_path = "http://server_path" + self.script_name = "script_name" + self.api_key = "api_key" # Proxy Server Tests def test_http_proxy_server(self): proxy_server = "someserver.com" http_proxy = proxy_server - sg = api.Shotgun(self.server_path, - self.script_name, - self.api_key, - http_proxy=http_proxy, - connect=False) + sg = api.Shotgun( + self.server_path, + self.script_name, + self.api_key, + http_proxy=http_proxy, + connect=False, + ) self.assertEqual(sg.config.proxy_server, proxy_server) self.assertEqual(sg.config.proxy_port, 8080) proxy_server = "123.456.789.012" http_proxy = proxy_server - sg = api.Shotgun(self.server_path, - self.script_name, - self.api_key, - http_proxy=http_proxy, - connect=False) + sg = api.Shotgun( + self.server_path, + self.script_name, + self.api_key, + http_proxy=http_proxy, + connect=False, + ) self.assertEqual(sg.config.proxy_server, proxy_server) self.assertEqual(sg.config.proxy_port, 8080) @@ -52,21 +57,25 @@ def test_http_proxy_server_and_port(self): proxy_server = "someserver.com" proxy_port = 1234 http_proxy = "%s:%d" % (proxy_server, proxy_port) - sg = api.Shotgun(self.server_path, - self.script_name, - self.api_key, - http_proxy=http_proxy, - connect=False) + sg = api.Shotgun( + self.server_path, + self.script_name, + self.api_key, + http_proxy=http_proxy, + connect=False, + ) self.assertEqual(sg.config.proxy_server, proxy_server) self.assertEqual(sg.config.proxy_port, proxy_port) proxy_server = "123.456.789.012" proxy_port = 1234 http_proxy = "%s:%d" % (proxy_server, proxy_port) - sg = api.Shotgun(self.server_path, - self.script_name, - self.api_key, - http_proxy=http_proxy, - connect=False) + sg = api.Shotgun( + self.server_path, + self.script_name, + self.api_key, + http_proxy=http_proxy, + connect=False, + ) self.assertEqual(sg.config.proxy_server, proxy_server) self.assertEqual(sg.config.proxy_port, proxy_port) @@ -75,13 +84,14 @@ def test_http_proxy_server_and_port_with_authentication(self): proxy_port = 1234 proxy_user = "user" proxy_pass = "password" - http_proxy = "%s:%s@%s:%d" % (proxy_user, proxy_pass, proxy_server, - proxy_port) - sg = api.Shotgun(self.server_path, - self.script_name, - self.api_key, - http_proxy=http_proxy, - connect=False) + http_proxy = "%s:%s@%s:%d" % (proxy_user, proxy_pass, proxy_server, proxy_port) + sg = api.Shotgun( + self.server_path, + self.script_name, + self.api_key, + http_proxy=http_proxy, + connect=False, + ) self.assertEqual(sg.config.proxy_server, proxy_server) self.assertEqual(sg.config.proxy_port, proxy_port) self.assertEqual(sg.config.proxy_user, proxy_user) @@ -90,13 +100,14 @@ def test_http_proxy_server_and_port_with_authentication(self): proxy_port = 1234 proxy_user = "user" proxy_pass = "password" - http_proxy = "%s:%s@%s:%d" % (proxy_user, proxy_pass, proxy_server, - proxy_port) - sg = api.Shotgun(self.server_path, - self.script_name, - self.api_key, - http_proxy=http_proxy, - connect=False) + http_proxy = "%s:%s@%s:%d" % (proxy_user, proxy_pass, proxy_server, proxy_port) + sg = api.Shotgun( + self.server_path, + self.script_name, + self.api_key, + http_proxy=http_proxy, + connect=False, + ) self.assertEqual(sg.config.proxy_server, proxy_server) self.assertEqual(sg.config.proxy_port, proxy_port) self.assertEqual(sg.config.proxy_user, proxy_user) @@ -107,13 +118,14 @@ def test_http_proxy_with_at_in_password(self): proxy_port = 1234 proxy_user = "user" proxy_pass = "p@ssword" - http_proxy = "%s:%s@%s:%d" % (proxy_user, proxy_pass, proxy_server, - proxy_port) - sg = api.Shotgun(self.server_path, - self.script_name, - self.api_key, - http_proxy=http_proxy, - connect=False) + http_proxy = "%s:%s@%s:%d" % (proxy_user, proxy_pass, proxy_server, proxy_port) + sg = api.Shotgun( + self.server_path, + self.script_name, + self.api_key, + http_proxy=http_proxy, + connect=False, + ) self.assertEqual(sg.config.proxy_server, proxy_server) self.assertEqual(sg.config.proxy_port, proxy_port) self.assertEqual(sg.config.proxy_user, proxy_user) @@ -121,63 +133,63 @@ def test_http_proxy_with_at_in_password(self): def test_malformatted_proxy_info(self): conn_info = { - 'base_url': self.server_path, - 'script_name': self.script_name, - 'api_key': self.api_key, - 'connect': False, + "base_url": self.server_path, + "script_name": self.script_name, + "api_key": self.api_key, + "connect": False, } - conn_info['http_proxy'] = 'http://someserver.com' + conn_info["http_proxy"] = "http://someserver.com" self.assertRaises(ValueError, api.Shotgun, **conn_info) - conn_info['http_proxy'] = 'user@someserver.com' + conn_info["http_proxy"] = "user@someserver.com" self.assertRaises(ValueError, api.Shotgun, **conn_info) - conn_info['http_proxy'] = 'someserver.com:1234:5678' + conn_info["http_proxy"] = "someserver.com:1234:5678" self.assertRaises(ValueError, api.Shotgun, **conn_info) class TestShotgunSummarize(unittest.TestCase): - '''Test case for _create_summary_request function and parameter + """Test case for _create_summary_request function and parameter validation as it exists in Shotgun.summarize. - Does not require database connection or test data.''' + Does not require database connection or test data.""" + def setUp(self): - self.sg = api.Shotgun('http://server_path', - 'script_name', - 'api_key', - connect=False) + self.sg = api.Shotgun( + "http://server_path", "script_name", "api_key", connect=False + ) def test_filter_operator_none(self): - expected_logical_operator = 'and' + expected_logical_operator = "and" filter_operator = None self._assert_filter_operator(expected_logical_operator, filter_operator) def _assert_filter_operator(self, expected_logical_operator, filter_operator): - result = self.get_call_rpc_params(None, {'filter_operator': filter_operator}) - actual_logical_operator = result['filters']['logical_operator'] + result = self.get_call_rpc_params(None, {"filter_operator": filter_operator}) + actual_logical_operator = result["filters"]["logical_operator"] self.assertEqual(expected_logical_operator, actual_logical_operator) def test_filter_operator_all(self): - expected_logical_operator = 'and' - filter_operator = 'all' + expected_logical_operator = "and" + filter_operator = "all" self._assert_filter_operator(expected_logical_operator, filter_operator) def test_filter_operator_or(self): - expected_logical_operator = 'or' - filter_operator = 'or' + expected_logical_operator = "or" + filter_operator = "or" self._assert_filter_operator(expected_logical_operator, filter_operator) def test_filters(self): - path = 'path' - relation = 'relation' - value = 'value' - expected_condition = {'path': path, 'relation': relation, 'values': [value]} - args = ['', [[path, relation, value]], None] + path = "path" + relation = "relation" + value = "value" + expected_condition = {"path": path, "relation": relation, "values": [value]} + args = ["", [[path, relation, value]], None] result = self.get_call_rpc_params(args, {}) - actual_condition = result['filters']['conditions'][0] + actual_condition = result["filters"]["conditions"][0] self.assertEqual(expected_condition, actual_condition) - @patch('shotgun_api3.Shotgun._call_rpc') + @patch("shotgun_api3.Shotgun._call_rpc") def get_call_rpc_params(self, args, kws, call_rpc): - '''Return params sent to _call_rpc from summarize.''' + """Return params sent to _call_rpc from summarize.""" if not args: args = [None, [], None] self.sg.summarize(*args, **kws) @@ -185,62 +197,72 @@ def get_call_rpc_params(self, args, kws, call_rpc): def test_grouping(self): result = self.get_call_rpc_params(None, {}) - self.assertFalse('grouping' in result) - grouping = ['something'] - kws = {'grouping': grouping} + self.assertFalse("grouping" in result) + grouping = ["something"] + kws = {"grouping": grouping} result = self.get_call_rpc_params(None, kws) - self.assertEqual(grouping, result['grouping']) + self.assertEqual(grouping, result["grouping"]) def test_grouping_type(self): - '''test_grouping_type tests that grouping parameter is a list or None''' - self.assertRaises(ValueError, self.sg.summarize, '', [], [], grouping='Not a list') + """test_grouping_type tests that grouping parameter is a list or None""" + self.assertRaises( + ValueError, self.sg.summarize, "", [], [], grouping="Not a list" + ) class TestShotgunBatch(unittest.TestCase): def setUp(self): - self.sg = api.Shotgun('http://server_path', - 'script_name', - 'api_key', - connect=False) + self.sg = api.Shotgun( + "http://server_path", "script_name", "api_key", connect=False + ) def test_missing_required_key(self): req = {} # requires keys request_type and entity_type self.assertRaises(api.ShotgunError, self.sg.batch, [req]) - req['entity_type'] = 'Entity' + req["entity_type"] = "Entity" self.assertRaises(api.ShotgunError, self.sg.batch, [req]) - req['request_type'] = 'not_real_type' + req["request_type"] = "not_real_type" self.assertRaises(api.ShotgunError, self.sg.batch, [req]) # create requires data key - req['request_type'] = 'create' + req["request_type"] = "create" self.assertRaises(api.ShotgunError, self.sg.batch, [req]) # update requires entity_id and data - req['request_type'] = 'update' - req['data'] = {} + req["request_type"] = "update" + req["data"] = {} self.assertRaises(api.ShotgunError, self.sg.batch, [req]) - del req['data'] - req['entity_id'] = 2334 + del req["data"] + req["entity_id"] = 2334 self.assertRaises(api.ShotgunError, self.sg.batch, [req]) # delete requires entity_id - req['request_type'] = 'delete' - del req['entity_id'] + req["request_type"] = "delete" + del req["entity_id"] self.assertRaises(api.ShotgunError, self.sg.batch, [req]) class TestServerCapabilities(unittest.TestCase): def test_no_server_version(self): - self.assertRaises(api.ShotgunError, api.shotgun.ServerCapabilities, 'host', {}) + self.assertRaises(api.ShotgunError, api.shotgun.ServerCapabilities, "host", {}) def test_bad_version(self): - '''test_bad_meta tests passing bad meta data type''' - self.assertRaises(api.ShotgunError, api.shotgun.ServerCapabilities, 'host', {'version': (0, 0, 0)}) + """test_bad_meta tests passing bad meta data type""" + self.assertRaises( + api.ShotgunError, + api.shotgun.ServerCapabilities, + "host", + {"version": (0, 0, 0)}, + ) def test_dev_version(self): - serverCapabilities = api.shotgun.ServerCapabilities('host', {'version': (3, 4, 0, 'Dev')}) + serverCapabilities = api.shotgun.ServerCapabilities( + "host", {"version": (3, 4, 0, "Dev")} + ) self.assertEqual(serverCapabilities.version, (3, 4, 0)) self.assertTrue(serverCapabilities.is_dev) - serverCapabilities = api.shotgun.ServerCapabilities('host', {'version': (2, 4, 0)}) + serverCapabilities = api.shotgun.ServerCapabilities( + "host", {"version": (2, 4, 0)} + ) self.assertEqual(serverCapabilities.version, (2, 4, 0)) self.assertFalse(serverCapabilities.is_dev) @@ -248,13 +270,13 @@ def test_dev_version(self): class TestClientCapabilities(unittest.TestCase): def test_darwin(self): - self.assert_platform('Darwin', 'mac') + self.assert_platform("Darwin", "mac") def test_windows(self): - self.assert_platform('win32', 'windows') + self.assert_platform("win32", "windows") def test_linux(self): - self.assert_platform('Linux', 'linux') + self.assert_platform("Linux", "linux") def assert_platform(self, sys_ret_val, expected): platform = api.shotgun.sys.platform @@ -278,12 +300,12 @@ def test_no_platform(self): finally: api.shotgun.sys.platform = platform - @patch('shotgun_api3.shotgun.sys') + @patch("shotgun_api3.shotgun.sys") def test_py_version(self, mock_sys): major = 2 minor = 7 micro = 3 - mock_sys.version_info = (major, minor, micro, 'final', 0) + mock_sys.version_info = (major, minor, micro, "final", 0) expected_py_version = "%s.%s" % (major, minor) client_caps = api.shotgun.ClientCapabilities() self.assertEqual(client_caps.py_version, expected_py_version) @@ -293,26 +315,20 @@ class TestFilters(unittest.TestCase): maxDiff = None def test_empty(self): - expected = { - "logical_operator": "and", - "conditions": [] - } + expected = {"logical_operator": "and", "conditions": []} result = api.shotgun._translate_filters([], None) self.assertEqual(result, expected) def test_simple(self): - filters = [ - ["code", "is", "test"], - ["sg_status_list", "is", "ip"] - ] + filters = [["code", "is", "test"], ["sg_status_list", "is", "ip"]] expected = { "logical_operator": "or", "conditions": [ {"path": "code", "relation": "is", "values": ["test"]}, - {"path": "sg_status_list", "relation": "is", "values": ["ip"]} - ] + {"path": "sg_status_list", "relation": "is", "values": ["ip"]}, + ], } result = api.shotgun._translate_filters(filters, "any") @@ -323,20 +339,20 @@ def test_arrays(self): expected = { "logical_operator": "and", "conditions": [ - {"path": "code", "relation": "in", "values": ["test1", "test2", "test3"]} - ] + { + "path": "code", + "relation": "in", + "values": ["test1", "test2", "test3"], + } + ], } - filters = [ - ["code", "in", "test1", "test2", "test3"] - ] + filters = [["code", "in", "test1", "test2", "test3"]] result = api.shotgun._translate_filters(filters, "all") self.assertEqual(result, expected) - filters = [ - ["code", "in", ["test1", "test2", "test3"]] - ] + filters = [["code", "in", ["test1", "test2", "test3"]]] result = api.shotgun._translate_filters(filters, "all") self.assertEqual(result, expected) @@ -353,11 +369,11 @@ def test_nested(self): "filter_operator": "all", "filters": [ ["sg_status_list", "is", "hld"], - ["assets", "is", {"type": "Asset", "id": 9}] - ] - } - ] - } + ["assets", "is", {"type": "Asset", "id": 9}], + ], + }, + ], + }, ] expected = { @@ -372,13 +388,21 @@ def test_nested(self): { "logical_operator": "and", "conditions": [ - {"path": "sg_status_list", "relation": "is", "values": ["hld"]}, - {"path": "assets", "relation": "is", "values": [{"type": "Asset", "id": 9}]}, - ] - } - ] - } - ] + { + "path": "sg_status_list", + "relation": "is", + "values": ["hld"], + }, + { + "path": "assets", + "relation": "is", + "values": [{"type": "Asset", "id": 9}], + }, + ], + }, + ], + }, + ], } result = api.shotgun._translate_filters(filters, "all") @@ -386,27 +410,27 @@ def test_nested(self): def test_invalid(self): self.assertRaises(api.ShotgunError, api.shotgun._translate_filters, [], "bogus") - self.assertRaises(api.ShotgunError, api.shotgun._translate_filters, ["bogus"], "all") + self.assertRaises( + api.ShotgunError, api.shotgun._translate_filters, ["bogus"], "all" + ) - filters = [{ - "filter_operator": "bogus", - "filters": [] - }] + filters = [{"filter_operator": "bogus", "filters": []}] - self.assertRaises(api.ShotgunError, api.shotgun._translate_filters, filters, "all") + self.assertRaises( + api.ShotgunError, api.shotgun._translate_filters, filters, "all" + ) - filters = [{ - "filters": [] - }] + filters = [{"filters": []}] - self.assertRaises(api.ShotgunError, api.shotgun._translate_filters, filters, "all") + self.assertRaises( + api.ShotgunError, api.shotgun._translate_filters, filters, "all" + ) - filters = [{ - "filter_operator": "all", - "filters": {"bogus": "bogus"} - }] + filters = [{"filter_operator": "all", "filters": {"bogus": "bogus"}}] - self.assertRaises(api.ShotgunError, api.shotgun._translate_filters, filters, "all") + self.assertRaises( + api.ShotgunError, api.shotgun._translate_filters, filters, "all" + ) @mock.patch.dict(os.environ, {"SHOTGUN_API_DISABLE_ENTITY_OPTIMIZATION": "1"}) def test_related_object(self): @@ -414,7 +438,13 @@ def test_related_object(self): [ "project", "is", - {"foo": "foo", "bar": "bar", "id": 999, "baz": "baz", "type": "Anything"}, + { + "foo": "foo", + "bar": "bar", + "id": 999, + "baz": "baz", + "type": "Anything", + }, ], ] expected = { @@ -445,7 +475,13 @@ def test_related_object_entity_optimization_is(self): [ "project", "is", - {"foo": "foo", "bar": "bar", "id": 999, "baz": "baz", "type": "Anything"}, + { + "foo": "foo", + "bar": "bar", + "id": 999, + "baz": "baz", + "type": "Anything", + }, ], ] expected = { @@ -481,7 +517,7 @@ def test_related_object_entity_optimization_is(self): { "path": "something", "relation": "is", - "values": [{'bar': 'bar', 'foo': 'foo'}], + "values": [{"bar": "bar", "foo": "foo"}], } ], } @@ -496,8 +532,20 @@ def test_related_object_entity_optimization_in(self): "project", "in", [ - {"foo1": "foo1", "bar1": "bar1", "id": 999, "baz1": "baz1", "type": "Anything"}, - {"foo2": "foo2", "bar2": "bar2", "id": 998, "baz2": "baz2", "type": "Anything"}, + { + "foo1": "foo1", + "bar1": "bar1", + "id": 999, + "baz1": "baz1", + "type": "Anything", + }, + { + "foo2": "foo2", + "bar2": "bar2", + "id": 998, + "baz2": "baz2", + "type": "Anything", + }, {"foo3": "foo3", "bar3": "bar3"}, ], ], @@ -520,7 +568,7 @@ def test_related_object_entity_optimization_in(self): { "foo3": "foo3", "bar3": "bar3", - } + }, ], } ], @@ -560,7 +608,9 @@ def test_related_object_update_entity(self): ], } sg = api.Shotgun("http://server_path", "script_name", "api_key", connect=False) - result = sg._translate_update_params(entity_type, entity_id, data, multi_entity_update_modes) + result = sg._translate_update_params( + entity_type, entity_id, data, multi_entity_update_modes + ) self.assertEqual(result, expected) @mock.patch("shotgun_api3.shotgun.SHOTGUN_API_DISABLE_ENTITY_OPTIMIZATION", False) @@ -611,7 +661,9 @@ def test_related_object_update_optimization_entity(self): ], } sg = api.Shotgun("http://server_path", "script_name", "api_key", connect=False) - result = sg._translate_update_params(entity_type, entity_id, data, multi_entity_update_modes) + result = sg._translate_update_params( + entity_type, entity_id, data, multi_entity_update_modes + ) self.assertEqual(result, expected) @mock.patch("shotgun_api3.shotgun.SHOTGUN_API_DISABLE_ENTITY_OPTIMIZATION", False) @@ -625,7 +677,11 @@ def test_related_object_update_optimization_entity_multi(self): {"id": 6441, "type": "Asset", "name": "disposable name 6441"}, {"id": 6440, "type": "Asset"}, ], - "sg_class": {"id": 1, "type": "CustomEntity53", "name": "disposable name 1"}, + "sg_class": { + "id": 1, + "type": "CustomEntity53", + "name": "disposable name 1", + }, } expected = { "type": "Asset", @@ -640,7 +696,10 @@ def test_related_object_update_optimization_entity_multi(self): {"id": 6440, "type": "Asset"}, ], }, - {"field_name": "sg_class", "value": {"type": "CustomEntity53", "id": 1}}, + { + "field_name": "sg_class", + "value": {"type": "CustomEntity53", "id": 1}, + }, ], } sg = api.Shotgun("http://server_path", "script_name", "api_key", connect=False) @@ -662,10 +721,9 @@ class TestCerts(unittest.TestCase): ] def setUp(self): - self.sg = api.Shotgun('http://server_path', - 'script_name', - 'api_key', - connect=False) + self.sg = api.Shotgun( + "http://server_path", "script_name", "api_key", connect=False + ) # Get the location of the certs file self.certs = self.sg._get_certs_file(None) @@ -712,7 +770,12 @@ def test_httplib(self): certificate with httplib. """ # First check that we get an error when trying to connect to a known dummy bad URL - self.assertRaises(ssl_error_classes, self._check_url_with_sg_api_httplib2, self.bad_url, self.certs) + self.assertRaises( + ssl_error_classes, + self._check_url_with_sg_api_httplib2, + self.bad_url, + self.certs, + ) # Now check that the good urls connect properly using the certs for url in self.test_urls: @@ -725,12 +788,14 @@ def test_urlib(self): certificate with urllib. """ # First check that we get an error when trying to connect to a known dummy bad URL - self.assertRaises(urllib.error.URLError, self._check_url_with_urllib, self.bad_url) + self.assertRaises( + urllib.error.URLError, self._check_url_with_urllib, self.bad_url + ) # Now check that the good urls connect properly using the certs for url in self.test_urls: response = self._check_url_with_urllib(url) - assert (response is not None) + assert response is not None class TestMimetypesFix(unittest.TestCase): @@ -738,8 +803,10 @@ class TestMimetypesFix(unittest.TestCase): Makes sure that the mimetypes fix will be imported. """ - @patch('shotgun_api3.shotgun.sys') - def _test_mimetypes_import(self, platform, major, minor, patch_number, result, mock): + @patch("shotgun_api3.shotgun.sys") + def _test_mimetypes_import( + self, platform, major, minor, patch_number, result, mock + ): """ Mocks sys.platform and sys.version_info to test the mimetypes import code. """ @@ -749,5 +816,5 @@ def _test_mimetypes_import(self, platform, major, minor, patch_number, result, m self.assertEqual(_is_mimetypes_broken(), result) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/update_httplib2.py b/update_httplib2.py index acfdee2b7..30422e0c2 100755 --- a/update_httplib2.py +++ b/update_httplib2.py @@ -17,18 +17,20 @@ class Utilities: def download_archive(self, file_path, file_name): """Download the archive from github.""" print(f"Downloading {file_name}") - subprocess.check_output([ - "curl", - "-L", - f"https://github.com/httplib2/httplib2/archive/{file_name}", - "-o", - file_path]) + subprocess.check_output( + [ + "curl", + "-L", + f"https://github.com/httplib2/httplib2/archive/{file_name}", + "-o", + file_path, + ] + ) def unzip_archive(self, file_path, file_name, temp_dir): """Unzip in a temp dir.""" print(f"Unzipping {file_name}") - subprocess.check_output( - ["unzip", str(file_path), "-d", str(temp_dir)]) + subprocess.check_output(["unzip", str(file_path), "-d", str(temp_dir)]) def remove_folder(self, path): """Remove a folder recursively.""" @@ -38,11 +40,14 @@ def remove_folder(self, path): def git_remove(self, target): print(f"Removing {target} in git.") try: - subprocess.check_output([ - "git", - "rm", - "-rf", - ] + target) + subprocess.check_output( + [ + "git", + "rm", + "-rf", + ] + + target + ) except Exception as e: pass @@ -58,8 +63,8 @@ def sanitize_file(self, file_path): contents = contents.replace("from httplib2.", "from .") contents = contents.replace("from httplib2", "from .") contents = contents.replace( - "import pyparsing as pp", - "from ... import pyparsing as pp") + "import pyparsing as pp", "from ... import pyparsing as pp" + ) with open(file_path, "w") as f: f.write(contents) @@ -88,18 +93,13 @@ def main(temp_path, repo_root, version): utilities.remove_folder(python3_dir) # Removes the previous version of httplib2 - utilities.git_remove([ - str(python2_dir), - str(python3_dir) - ]) + utilities.git_remove([str(python2_dir), str(python3_dir)]) # Copies a new version into place. print("Copying new version of httplib2") root_folder = unzipped_folder / f"httplib2-{version[1:]}" - utilities.copy_folder( - str(root_folder / "python2" / "httplib2"), python2_dir) - utilities.copy_folder( - str(root_folder / "python3" / "httplib2"), python3_dir) + utilities.copy_folder(str(root_folder / "python2" / "httplib2"), python2_dir) + utilities.copy_folder(str(root_folder / "python3" / "httplib2"), python3_dir) utilities.remove_folder(f"{python2_dir}/test") utilities.remove_folder(f"{python3_dir}/test") From b0d4a809aeea7713057c51a34dadef83d0e52d47 Mon Sep 17 00:00:00 2001 From: Julien Langlois <16244608+julien-lang@users.noreply.github.com> Date: Thu, 20 Feb 2025 11:55:27 -0800 Subject: [PATCH 077/125] SG-37280 Update Software Credits (#369) CY2024 Update --- software_credits | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/software_credits b/software_credits index 819f81ee6..6fa0bf725 100644 --- a/software_credits +++ b/software_credits @@ -1,7 +1,21 @@ -The Flow Production Tracking Python API uses the following software. Thanks to their creators, license information below. +The Flow Production Tracking Python API uses the following software. +Thanks to their creators, license information below. ============================== PYTHON ============================== +Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, +2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 Python Software Foundation. All +rights reserved. + +Copyright (c) 2000 BeOpen.com. +All rights reserved. + +Copyright (c) 1995-2001 Corporation for National Research Initiatives. +All rights reserved. + +Copyright (c) 1991-1995 Stichting Mathematisch Centrum. +All rights reserved. + 1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and the Individual or Organization ("Licensee") accessing and otherwise using Python 2.7.17 software in source or binary form and its associated documentation. @@ -47,9 +61,13 @@ The Flow Production Tracking Python API uses the following software. Thanks to t ============================== Certifi ============================== -This Autodesk software contains the python-certifi package and is subject to the -terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not -distributed with this file, You can obtain one at https://mozilla.org/MPL/2.0/. +Copyright © 2024 Contributors +This Autodesk software contains the unmodified python-certifi 2024.07.04 +package. The use and distribution terms for this software are covered by the +Mozilla Public License 2.0 (https://www.mozilla.org/en-US/MPL/2.0/ ). By using +this software in any fashion, you are agreeing to be bound by the terms of this +license. The source code for python-certifi is available from +https://github.com/certifi/python-certifi/releases/tag/2024.07.04 ============================== Httplib2 ============================== @@ -104,7 +122,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ============================== SIX ============================== -Copyright (c) 2010-2019 Benjamin Peterson +Copyright (c) 2010-2020 Benjamin Peterson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in From 8b037619e079616f60f45e860d3764c8b38a20bc Mon Sep 17 00:00:00 2001 From: Carlos Villavicencio <123113322+carlos-villavicencio-adsk@users.noreply.github.com> Date: Thu, 20 Feb 2025 14:58:03 -0500 Subject: [PATCH 078/125] Fix Changelog typo (#370) --- HISTORY.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HISTORY.rst b/HISTORY.rst index 9858f61c8..a2981b9c2 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -4,7 +4,7 @@ Flow Production Tracking Python API Changelog Here you can see the full list of changes between each Python API release. -v3.8.0 (2024 Feb 7) +v3.8.0 (2025 Feb 7) =================== - Extend the payload optimizations to the ``in`` and ``not_in`` filters and From c0eec8f2ca6e75963f03c92ae417763983d7a3a3 Mon Sep 17 00:00:00 2001 From: Carlos Villavicencio <123113322+carlos-villavicencio-adsk@users.noreply.github.com> Date: Tue, 25 Feb 2025 11:32:31 -0500 Subject: [PATCH 079/125] Packaging for 3.8.1 (#371) --- HISTORY.rst | 7 +++++++ setup.py | 2 +- shotgun_api3/shotgun.py | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index a2981b9c2..47b375bfb 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -4,6 +4,13 @@ Flow Production Tracking Python API Changelog Here you can see the full list of changes between each Python API release. +v3.8.1 (2025 Feb 25) +==================== + +- Upgrade certifi to 2024.12.14. +- Apply black 25.1.0 formatting to the source code. +- Update Software Credits + v3.8.0 (2025 Feb 7) =================== diff --git a/setup.py b/setup.py index f92018fe1..2d982ec22 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ setup( name="shotgun_api3", - version="3.8.0", + version="3.8.1", description="Flow Production Tracking Python API", long_description=readme, author="Autodesk", diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index a805fa5f4..455ed477e 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -122,7 +122,7 @@ def _is_mimetypes_broken(): # ---------------------------------------------------------------------------- # Version -__version__ = "3.8.0" +__version__ = "3.8.1" # ---------------------------------------------------------------------------- # Errors From c06aff4c3132dcb10d4ee4a3856cccea9f875ee9 Mon Sep 17 00:00:00 2001 From: Julien Langlois <16244608+julien-lang@users.noreply.github.com> Date: Thu, 27 Feb 2025 11:42:16 -0800 Subject: [PATCH 080/125] SG-38301 Review the documentation regarding the SHOTGUN_API_CACERTS variable (#373) Align wording with tk-core --- docs/reference.rst | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/docs/reference.rst b/docs/reference.rst index 658fb6b4a..77241f052 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -936,8 +936,19 @@ Environment Variables SHOTGUN_API_CACERTS =================== -Used to specify a path to an external SSL certificates file. This environment variable can be used in place of the ``ca_certs`` keyword argument to the :class:`~shotgun.Shotgun` constructor. In the case that both this environment variable is set and the keyword argument is provided, the value from the keyword argument will be used. - +Use this variable to override the default Trusted Root Certification Authorities +Certificate Store bundled with this library. +By default, the library relies on `certifi `_ +as its Root CA store. + +This environment variable can be used in place of the ``ca_certs`` keyword +argument to the :class:`~shotgun.Shotgun` constructor. +In the case that both this environment variable is set and the keyword argument +is provided, the value from the keyword argument will be used. + +For an example about using ``SHOTGUN_API_CACERTS`` to fix a certificate issue, +see the `SSLHandshakeError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed `_ +article. SHOTGUN_API_RETRY_INTERVAL ========================== From e4d5576431d61f8d98861245836337331bbe14cd Mon Sep 17 00:00:00 2001 From: Carlos Villavicencio <123113322+carlos-villavicencio-adsk@users.noreply.github.com> Date: Thu, 6 Mar 2025 13:19:41 -0500 Subject: [PATCH 081/125] Update README.md (#374) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f960e941f..4c9e464df 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -[![VFX Platform](https://img.shields.io/badge/vfxplatform-2024%20%7C%202023%20%7C%202022%20%7C%202021-blue.svg)](http://www.vfxplatform.com/) -[![Python](https://img.shields.io/badge/python-3.7%20%7C%203.9%20%7C%203.10%20%7C%203.11-blue.svg)](https://www.python.org/) +[![VFX Platform](https://img.shields.io/badge/vfxplatform-2025%20%7C%202024%20%7C%202023%20%7C%202022-blue.svg)](http://www.vfxplatform.com/) +[![Python](https://img.shields.io/badge/python-3.11%20%7C%203.10%20%7C%203.9-blue.svg)](https://www.python.org/)/) [![Reference Documentation](http://img.shields.io/badge/doc-reference-blue.svg)](http://developer.shotgridsoftware.com/python-api) [![Build Status](https://dev.azure.com/shotgun-ecosystem/Python%20API/_apis/build/status/shotgunsoftware.python-api?branchName=master)](https://dev.azure.com/shotgun-ecosystem/Python%20API/_build/latest?definitionId=108&branchName=master) [![Coverage Status](https://coveralls.io/repos/github/shotgunsoftware/python-api/badge.svg?branch=master)](https://coveralls.io/github/shotgunsoftware/python-api?branch=master) From 666fa0c4ee33c432d6e3b0228363c1f9c9a08e9e Mon Sep 17 00:00:00 2001 From: Carlos Villavicencio <123113322+carlos-villavicencio-adsk@users.noreply.github.com> Date: Tue, 11 Mar 2025 16:02:28 -0500 Subject: [PATCH 082/125] SG-35712 Prevent flaky disconnection when uploading thumbnails on publish (#368) * Handle opening the connection in the retry block * Update parent * Revert parent class for `CACertsHTTPSConnection` * Remove testing argument --- shotgun_api3/shotgun.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index 455ed477e..e1beaac01 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -4504,16 +4504,16 @@ def _upload_data_to_storage(self, data, content_type, size, storage_url): :returns: upload url. :rtype: str """ - opener = self._build_opener(urllib.request.HTTPHandler) - - request = urllib.request.Request(storage_url, data=data) - request.add_header("Content-Type", content_type) - request.add_header("Content-Length", size) - request.get_method = lambda: "PUT" attempt = 1 while attempt <= self.MAX_ATTEMPTS: try: + opener = self._build_opener(urllib.request.HTTPHandler) + + request = urllib.request.Request(storage_url, data=data) + request.add_header("Content-Type", content_type) + request.add_header("Content-Length", size) + request.get_method = lambda: "PUT" result = self._make_upload_request(request, opener) LOG.debug("Completed request to %s" % request.get_method()) @@ -4634,12 +4634,12 @@ def _send_form(self, url, params): params.update(self._auth_params()) - opener = self._build_opener(FormPostHandler) attempt = 1 while attempt <= self.MAX_ATTEMPTS: # Perform the request try: + opener = self._build_opener(FormPostHandler) resp = opener.open(url, params) result = resp.read() # response headers are in str(resp.info()).splitlines() @@ -4679,11 +4679,11 @@ def __init__(self, *args, **kwargs): """ # Pop that argument, self.__ca_certs = kwargs.pop("ca_certs") - http_client.HTTPConnection.__init__(self, *args, **kwargs) + super().__init__(self, *args, **kwargs) def connect(self): "Connect to a host on a given (SSL) port." - http_client.HTTPConnection.connect(self) + super().connect(self) # Now that the regular HTTP socket has been created, wrap it with our SSL certs. if six.PY38: context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) @@ -4698,13 +4698,13 @@ def connect(self): ) -class CACertsHTTPSHandler(urllib.request.HTTPSHandler): +class CACertsHTTPSHandler(urllib.request.HTTPHandler): """ Handler that ensures https connections are created with the custom CA certs. """ def __init__(self, cacerts): - urllib.request.HTTPSHandler.__init__(self) + super().__init__(self) self.__ca_certs = cacerts def https_open(self, req): From 4508d1cd937caee631ad3041447978aeb7a012ac Mon Sep 17 00:00:00 2001 From: Carlos Villavicencio <123113322+carlos-villavicencio-adsk@users.noreply.github.com> Date: Tue, 11 Mar 2025 16:21:17 -0500 Subject: [PATCH 083/125] Packaging for 3.8.2 (#375) * Packaging for 3.8.2 * Format file * Fix format --- HISTORY.rst | 7 +++++++ setup.py | 2 +- shotgun_api3/shotgun.py | 3 +-- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 47b375bfb..54b30f217 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -4,6 +4,13 @@ Flow Production Tracking Python API Changelog Here you can see the full list of changes between each Python API release. +v3.8.2 (2025 Mar 11) +==================== + +- Prevent flaky disconnection when uploading thumbnails on publish. + There's a flaky disconnection when the publisher uploads the thumbnail to the server. + The most common errors were: ``Connection closed by peer`` and ``URLopen error EOF occurred in violation of protocol ssl.c:1006``. + v3.8.1 (2025 Feb 25) ==================== diff --git a/setup.py b/setup.py index 2d982ec22..cf3304f91 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ setup( name="shotgun_api3", - version="3.8.1", + version="3.8.2", description="Flow Production Tracking Python API", long_description=readme, author="Autodesk", diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index e1beaac01..b682e87eb 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -122,7 +122,7 @@ def _is_mimetypes_broken(): # ---------------------------------------------------------------------------- # Version -__version__ = "3.8.1" +__version__ = "3.8.2" # ---------------------------------------------------------------------------- # Errors @@ -4634,7 +4634,6 @@ def _send_form(self, url, params): params.update(self._auth_params()) - attempt = 1 while attempt <= self.MAX_ATTEMPTS: # Perform the request From 418d72e37c518a67abee10cf8094a633e59e1caf Mon Sep 17 00:00:00 2001 From: Carlos Villavicencio <123113322+carlos-villavicencio-adsk@users.noreply.github.com> Date: Mon, 14 Apr 2025 09:26:07 -0500 Subject: [PATCH 084/125] Remove Python 3.7 from CI (#377) * Remove Python 3.7 from CI * Update changes from backend --- azure-pipelines-templates/run-tests.yml | 2 -- tests/test_api.py | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/azure-pipelines-templates/run-tests.yml b/azure-pipelines-templates/run-tests.yml index 831c276ec..bc402c42c 100644 --- a/azure-pipelines-templates/run-tests.yml +++ b/azure-pipelines-templates/run-tests.yml @@ -44,8 +44,6 @@ jobs: strategy: matrix: # We support these versions of Python. - Python37: - python.version: '3.7' Python39: python.version: '3.9' Python310: diff --git a/tests/test_api.py b/tests/test_api.py index 0e611316a..d07442e96 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1004,6 +1004,7 @@ def test_preferences_read(self): resp = self.sg.preferences_read() expected = { + "creative_review_settings": "", "date_component_order": "month_day", "duration_units": "days", "format_currency_fields_decimal_options": "$1,000.99", From 17620c9eaa058545cb75796679b15c3654a0a3e7 Mon Sep 17 00:00:00 2001 From: Carlos Villavicencio <123113322+carlos-villavicencio-adsk@users.noreply.github.com> Date: Mon, 14 Apr 2025 09:57:58 -0500 Subject: [PATCH 085/125] SG-37203 Apply mockgun improvements (#376) * Apply #217 * Fix dict * Apply #376 * Apply #364 --- shotgun_api3/lib/mockgun/mockgun.py | 46 +++++- tests/test_mockgun.py | 231 +++++++++++++++++++++++++++- 2 files changed, 266 insertions(+), 11 deletions(-) diff --git a/shotgun_api3/lib/mockgun/mockgun.py b/shotgun_api3/lib/mockgun/mockgun.py index 36b98dd5d..18e4a142c 100644 --- a/shotgun_api3/lib/mockgun/mockgun.py +++ b/shotgun_api3/lib/mockgun/mockgun.py @@ -293,6 +293,25 @@ def find( # handle the ordering of the recordset if order: # order: [{"field_name": "code", "direction": "asc"}, ... ] + def sort_none(k, order_field): + """ + Handle sorting of None consistently. + Note: Doesn't handle [checkbox, serializable, url]. + """ + field_type = self._get_field_type(k["type"], order_field) + value = k[order_field] + if value is not None: + return value + elif field_type in ("number", "percent", "duration"): + return 0 + elif field_type == "float": + return 0.0 + elif field_type in ("text", "entity_type", "date", "list", "status_list"): + return "" + elif field_type == "date_time": + return datetime.datetime(datetime.MINYEAR, 1, 1) + return None + for order_entry in order: if "field_name" not in order_entry: raise ValueError("Order clauses must be list of dicts with keys 'field_name' and 'direction'!") @@ -305,7 +324,11 @@ def find( else: raise ValueError("Unknown ordering direction") - results = sorted(results, key=lambda k: k[order_field], reverse=desc_order) + results = sorted( + results, + key=lambda k: sort_none(k, order_field), + reverse=desc_order, + ) if fields is None: fields = set(["type", "id"]) @@ -608,6 +631,20 @@ def _compare(self, field_type, lval, operator, rval): if operator == "is": return lval == rval elif field_type == "text": + # Some operations expect a list but can deal with a single value + if operator in ("in", "not_in") and not isinstance(rval, list): + rval = [rval] + # Some operation expect a string but can deal with None + elif operator in ("starts_with", "ends_with", "contains", "not_contains"): + lval = lval or '' + rval = rval or '' + # Shotgun string comparison is case insensitive + lval = lval.lower() if lval is not None else None + if isinstance(rval, list): + rval = [val.lower() if val is not None else None for val in rval] + else: + rval = rval.lower() if rval is not None else None + if operator == "is": return lval == rval elif operator == "is_not": @@ -617,7 +654,7 @@ def _compare(self, field_type, lval, operator, rval): elif operator == "contains": return rval in lval elif operator == "not_contains": - return lval not in rval + return rval not in lval elif operator == "starts_with": return lval.startswith(rval) elif operator == "ends_with": @@ -831,7 +868,10 @@ def _update_row(self, entity_type, row, data, multi_entity_update_modes=None): update_mode = multi_entity_update_modes.get(field, "set") if multi_entity_update_modes else "set" if update_mode == "add": - row[field] += [{"type": item["type"], "id": item["id"]} for item in data[field]] + for item in data[field]: + new_item = {"type": item["type"], "id": item["id"]} + if new_item not in row[field]: + row[field].append(new_item) elif update_mode == "remove": row[field] = [ item diff --git a/tests/test_mockgun.py b/tests/test_mockgun.py index 1395355fa..e7e4295e4 100644 --- a/tests/test_mockgun.py +++ b/tests/test_mockgun.py @@ -35,6 +35,7 @@ and can be run on their own by typing "python test_mockgun.py". """ +import datetime import re import os import unittest @@ -188,14 +189,171 @@ def setUp(self): self._mockgun = Mockgun( "https://test.shotgunstudio.com", login="user", password="1234" ) - self._user = self._mockgun.create("HumanUser", {"login": "user"}) + self._user1 = self._mockgun.create("HumanUser", {"login": "user"}) + self._user2 = self._mockgun.create("HumanUser", {"login": None}) + + def test_operator_is(self): + """ + Ensure is operator work. + """ + actual = self._mockgun.find("HumanUser", [["login", "is", "user"]]) + expected = [{"type": "HumanUser", "id": self._user1["id"]}] + self.assertEqual(expected, actual) + + def test_operator_is_none(self): + """ + Ensure is operator work when used with None. + """ + actual = self._mockgun.find("HumanUser", [["login", "is", None]]) + expected = [{"type": "HumanUser", "id": self._user2["id"]}] + self.assertEqual(expected, actual) + + def test_operator_is_case_sensitivity(self): + """ + Ensure is operator is case insensitive. + """ + actual = self._mockgun.find("HumanUser", [["login", "is", "USER"]]) + expected = [{"type": "HumanUser", "id": self._user1["id"]}] + self.assertEqual(expected, actual) + + def test_operator_is_not(self): + """ + Ensure the is_not operator works. + """ + actual = self._mockgun.find("HumanUser", [["login", "is_not", "user"]]) + expected = [{"type": "HumanUser", "id": self._user2["id"]}] + self.assertEqual(expected, actual) + + def test_operator_is_not_none(self): + """ + Ensure the is_not operator works when used with None. + """ + actual = self._mockgun.find("HumanUser", [["login", "is_not", None]]) + expected = [{"type": "HumanUser", "id": self._user1["id"]}] + self.assertEqual(expected, actual) + + def test_operator_is_not_case_sensitivity(self): + """ + Ensure the is_not operator is case insensitive. + """ + actual = self._mockgun.find("HumanUser", [["login", "is_not", "USER"]]) + expected = [{"type": "HumanUser", "id": self._user2["id"]}] + self.assertEqual(expected, actual) + + def test_operator_in(self): + """ + Ensure the in operator works. + """ + actual = self._mockgun.find("HumanUser", [["login", "in", ["user"]]]) + expected = [{"type": "HumanUser", "id": self._user1["id"]}] + self.assertEqual(expected, actual) + + def test_operator_in_none(self): + """ + Ensure the in operator works with a list containing None. + """ + actual = self._mockgun.find("HumanUser", [["login", "in", [None]]]) + expected = [{"type": "HumanUser", "id": self._user2["id"]}] + self.assertEqual(expected, actual) + + def test_operator_in_case_sensitivity(self): + """ + Ensure the in operator is case insensitive. + """ + actual = self._mockgun.find("HumanUser", [["login", "in", ["USER"]]]) + expected = [{"type": "HumanUser", "id": self._user1["id"]}] + self.assertEqual(expected, actual) + + def test_operator_not_in(self): + """ + Ensure the not_in operator works. + """ + actual = self._mockgun.find("HumanUser", [["login", "not_in", ["foo"]]]) + expected = [ + {"type": "HumanUser", "id": self._user1["id"]}, + {"type": "HumanUser", "id": self._user2["id"]}, + ] + self.assertEqual(expected, actual) + + def test_operator_not_in_none(self): + """ + Ensure the not_not operator works with a list containing None. + """ + actual = self._mockgun.find("HumanUser", [["login", "not_in", [None]]]) + expected = [{"type": "HumanUser", "id": self._user1["id"]}] + self.assertEqual(expected, actual) + + def test_operator_not_in_case_sensitivity(self): + """ + Ensure the not_in operator is case insensitive. + """ + actual = self._mockgun.find("HumanUser", [["login", "not_in", ["USER"]]]) + expected = [{"type": "HumanUser", "id": self._user2["id"]}] + self.assertEqual(expected, actual) def test_operator_contains(self): """ - Ensures contains operator works. + Ensures the contains operator works. """ - item = self._mockgun.find_one("HumanUser", [["login", "contains", "se"]]) - self.assertTrue(item) + actual = self._mockgun.find("HumanUser", [["login", "contains", "se"]]) + expected = [{"type": "HumanUser", "id": self._user1["id"]}] + self.assertEqual(expected, actual) + + def test_operator_contains_case_sensitivity(self): + """ + Ensure the contains operator is case insensitive. + """ + actual = self._mockgun.find("HumanUser", [["login", "contains", "SE"]]) + expected = [{"type": "HumanUser", "id": self._user1["id"]}] + self.assertEqual(expected, actual) + + def test_operator_not_contains(self): + """ + Ensure the not_contains operator works. + """ + actual = self._mockgun.find("HumanUser", [["login", "not_contains", "user"]]) + expected = [{"type": "HumanUser", "id": self._user2["id"]}] + self.assertEqual(expected, actual) + + def test_operator_not_contains_case_sensitivity(self): + """ + Ensure the not_contains operator is case insensitive. + """ + actual = self._mockgun.find("HumanUser", [["login", "not_contains", "USER"]]) + expected = [{"type": "HumanUser", "id": self._user2["id"]}] + self.assertEqual(expected, actual) + + def test_operator_starts_with(self): + """ + Ensure the starts_with operator works. + """ + actual = self._mockgun.find("HumanUser", [["login", "starts_with", "us"]]) + expected = [{"type": "HumanUser", "id": self._user1["id"]}] + self.assertEqual(expected, actual) + + def test_operator_starts_with_case_sensitivity(self): + """ + Ensure the starts_with operator is case insensitive. + """ + actual = self._mockgun.find("HumanUser", [["login", "starts_with", "US"]]) + expected = [{"type": "HumanUser", "id": self._user1["id"]}] + self.assertEqual(expected, actual) + + def test_operator_ends_with(self): + """ + Ensure the ends_with operator works. + """ + actual = self._mockgun.find("HumanUser", [["login", "ends_with", "er"]]) + expected = [{"type": "HumanUser", "id": self._user1["id"]}] + self.assertEqual(expected, actual) + + def test_operator_ends_with_case_sensitivity(self): + """ + Ensure the starts_with operator is case insensitive. + """ + actual = self._mockgun.find("HumanUser", [["login", "ends_with", "ER"]]) + expected = [{"type": "HumanUser", "id": self._user1["id"]}] + self.assertEqual(expected, actual) class TestMultiEntityFieldComparison(unittest.TestCase): @@ -345,10 +503,12 @@ def test_update_add(self): """ Ensures that "add" multi_entity_update_mode works. """ + # Attempts to add _version2 + # It already exists on the playlist and should not be duplicated self._mockgun.update( "Playlist", self._add_playlist["id"], - {"versions": [self._version3]}, + {"versions": [self._version2, self._version3]}, multi_entity_update_modes={"versions": "add"}, ) @@ -429,15 +589,29 @@ def setUp(self): self._prj2_link = self._mockgun.create("Project", {"name": "prj2"}) self._shot1 = self._mockgun.create( - "Shot", {"code": "shot1", "project": self._prj1_link} + "Shot", + { + "code": "shot1", + "project": self._prj1_link, + "description": "a", + "sg_cut_order": 2, + }, ) self._shot2 = self._mockgun.create( - "Shot", {"code": "shot2", "project": self._prj1_link} + "Shot", {"code": "shot2", "project": self._prj1_link, "sg_cut_order": 1} ) self._shot3 = self._mockgun.create( - "Shot", {"code": "shot3", "project": self._prj2_link} + "Shot", {"code": "shot3", "project": self._prj2_link, "description": "b"} + ) + + self._user1 = self._mockgun.create( + "HumanUser", {"login": "user1", "password_strength": 0.2} + ) + + self._user2 = self._mockgun.create( + "HumanUser", {"login": "user2", "created_at": datetime.datetime(2025, 1, 1)} ) def test_simple_filter_operators(self): @@ -468,6 +642,47 @@ def test_simple_filter_operators(self): self.assertEqual(len(shots), 0) + def test_ordered_filter_operator(self): + """ + Test use of the order feature of filter_operator on supported data types. + """ + find_args = ["Shot", [], ["code"]] + + # str field + shots = self._mockgun.find( + *find_args, order=[{"field_name": "description", "direction": "asc"}] + ) + self.assertEqual([s["code"] for s in shots], ["shot2", "shot1", "shot3"]) + + shots = self._mockgun.find( + *find_args, order=[{"field_name": "description", "direction": "desc"}] + ) + self.assertEqual([s["code"] for s in shots], ["shot3", "shot1", "shot2"]) + + # int field + shots = self._mockgun.find( + *find_args, order=[{"field_name": "sg_cut_order", "direction": "asc"}] + ) + self.assertEqual([s["code"] for s in shots], ["shot3", "shot2", "shot1"]) + + # float field + users = self._mockgun.find( + "HumanUser", + [], + ["login"], + order=[{"field_name": "password_strength", "direction": "asc"}], + ) + self.assertEqual([u["login"] for u in users], ["user2", "user1"]) + + # date_time field + users = self._mockgun.find( + "HumanUser", + [], + ["login"], + order=[{"field_name": "created_at", "direction": "asc"}], + ) + self.assertEqual([u["login"] for u in users], ["user1", "user2"]) + def test_nested_filter_operators(self): """ Tests a the use of the filter_operator nested From 7f5f9c15119682de3141e54c6ca181fa6ed5479d Mon Sep 17 00:00:00 2001 From: Carlos Villavicencio <123113322+carlos-villavicencio-adsk@users.noreply.github.com> Date: Tue, 15 Apr 2025 11:13:46 -0500 Subject: [PATCH 086/125] Fix `creative_review_settings` test (#378) --- tests/test_api.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/test_api.py b/tests/test_api.py index d07442e96..788c9751b 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1004,7 +1004,6 @@ def test_preferences_read(self): resp = self.sg.preferences_read() expected = { - "creative_review_settings": "", "date_component_order": "month_day", "duration_units": "days", "format_currency_fields_decimal_options": "$1,000.99", @@ -1027,6 +1026,12 @@ def test_preferences_read(self): self.assertIn("view_master_settings", resp) resp.pop("view_master_settings") + # Simply make sure creative review settings are there. These change frequently and we + # don't want to have the test break because Creative Review changed or because we didn't + # update the test. + self.assertIn("creative_review_settings", resp) + resp.pop("creative_review_settings") + self.assertEqual(expected, resp) # all filtered From 5b3cf59fe13c96cc0639e6a501b5f84adf515e0e Mon Sep 17 00:00:00 2001 From: Carlos Villavicencio <123113322+carlos-villavicencio-adsk@users.noreply.github.com> Date: Thu, 15 May 2025 10:18:20 -0500 Subject: [PATCH 087/125] Fix reStructuredText format (#380) * Fix rst issue * Revert line * Update labels --- HISTORY.rst | 2 +- docs/cookbook/examples/basic_create_shot_task_template.rst | 4 ++-- docs/cookbook/examples/basic_delete_shot.rst | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 54b30f217..ad4ebda7a 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -61,7 +61,7 @@ v3.5.1 (2024 Apr 3) - Mockgun: add support for ``add_user_agent`` and ``set_session_uuid`` methods v3.5.0 (2024 Mar 26) -=================== +==================== - Rebranding component for Flow Production Tracking v3.4.2 (2024 Feb 6) diff --git a/docs/cookbook/examples/basic_create_shot_task_template.rst b/docs/cookbook/examples/basic_create_shot_task_template.rst index ab6248227..18722be96 100644 --- a/docs/cookbook/examples/basic_create_shot_task_template.rst +++ b/docs/cookbook/examples/basic_create_shot_task_template.rst @@ -42,8 +42,8 @@ created. wish to create by default on this Shot. We found the specific template we wanted to assign in the previous block by searching -Result ------- +Create Shot Result +------------------ The variable ``result`` now contains the dictionary of the new Shot that was created. :: diff --git a/docs/cookbook/examples/basic_delete_shot.rst b/docs/cookbook/examples/basic_delete_shot.rst index 4f2e91018..c79215e25 100644 --- a/docs/cookbook/examples/basic_delete_shot.rst +++ b/docs/cookbook/examples/basic_delete_shot.rst @@ -7,8 +7,8 @@ Deleting an entity in Flow Production Tracking is pretty straight-forward. No ex result = sg.delete("Shot", 40435) -Result ------- +Delete Shot Result +------------------ If the Shot was deleted successfully ``result`` will contain:: True From 8b8fdef211c5d19c4a15e17199b796249c416fb8 Mon Sep 17 00:00:00 2001 From: Carlos Villavicencio <123113322+carlos-villavicencio-adsk@users.noreply.github.com> Date: Thu, 15 May 2025 11:04:39 -0500 Subject: [PATCH 088/125] SG-27368 Fix more RSTs files for Sphinx (#381) * Fix more RSTs files for Sphinx * Update ref * Remove duplicate label --- docs/cookbook/examples/basic_create_shot.rst | 4 ++-- docs/cookbook/examples/basic_delete_shot.rst | 4 ++-- docs/cookbook/examples/basic_find_shot.rst | 4 ++-- docs/cookbook/examples/basic_update_shot.rst | 4 ++-- docs/cookbook/examples/svn_integration.rst | 2 +- docs/cookbook/tasks/split_tasks.rst | 4 ++-- docs/cookbook/tasks/updating_tasks.rst | 6 +++--- docs/reference.rst | 1 - 8 files changed, 14 insertions(+), 15 deletions(-) diff --git a/docs/cookbook/examples/basic_create_shot.rst b/docs/cookbook/examples/basic_create_shot.rst index 7513305fa..f7d412f89 100644 --- a/docs/cookbook/examples/basic_create_shot.rst +++ b/docs/cookbook/examples/basic_create_shot.rst @@ -51,8 +51,8 @@ this dictionary represents. It does not correspond to any field in Flow Producti Flow Production Tracking will *always* return the ``id`` and ``type`` keys in the dictionary when there are results representing an entity. -The Complete Example --------------------- +The Complete Example for creating a Shot +---------------------------------------- :: #!/usr/bin/env python diff --git a/docs/cookbook/examples/basic_delete_shot.rst b/docs/cookbook/examples/basic_delete_shot.rst index c79215e25..886b962d3 100644 --- a/docs/cookbook/examples/basic_delete_shot.rst +++ b/docs/cookbook/examples/basic_delete_shot.rst @@ -13,8 +13,8 @@ If the Shot was deleted successfully ``result`` will contain:: True -The Complete Example --------------------- +The Complete Example for deleting a Shot +---------------------------------------- :: #!/usr/bin/env python diff --git a/docs/cookbook/examples/basic_find_shot.rst b/docs/cookbook/examples/basic_find_shot.rst index 945eb1be6..4f5d73934 100644 --- a/docs/cookbook/examples/basic_find_shot.rst +++ b/docs/cookbook/examples/basic_find_shot.rst @@ -37,8 +37,8 @@ easier to read. So we'll add that to the import section of our script.:: import shotgun_api3 from pprint import pprint # useful for debugging -The Complete Example --------------------- +The Complete Example for finding a Shot +--------------------------------------- :: #!/usr/bin/env python diff --git a/docs/cookbook/examples/basic_update_shot.rst b/docs/cookbook/examples/basic_update_shot.rst index c2413c3ee..4e2055d49 100644 --- a/docs/cookbook/examples/basic_update_shot.rst +++ b/docs/cookbook/examples/basic_update_shot.rst @@ -40,8 +40,8 @@ It does not correspond to any field in Flow Production Tracking. Flow Production Tracking will *always* return the ``id`` and ``type`` keys in the dictionary when there are results representing an entity. -The Complete Example --------------------- +The Complete Example for updating a Shot +---------------------------------------- :: #!/usr/bin/env python diff --git a/docs/cookbook/examples/svn_integration.rst b/docs/cookbook/examples/svn_integration.rst index 8b0a6ce46..c676fd8ea 100644 --- a/docs/cookbook/examples/svn_integration.rst +++ b/docs/cookbook/examples/svn_integration.rst @@ -130,7 +130,7 @@ Explanation of selected lines: - line ``14``: This should be the URL to your instance of Flow Production Tracking. - lines ``15-16``: Make sure you get these values from the "Scripts" page in the Admin section of - the Flow Production Tracking web application. If you're not sure how to do this, check out :doc:`authentication`. + the Flow Production Tracking web application. If you're not sure how to do this, check out :ref:`authentication`. - line ``17``: This is the address of Trac, our web-based interface that we use with Subversion. You may use a different interface, or none at all, so feel free to adjust this line or ignore it as your case may be. diff --git a/docs/cookbook/tasks/split_tasks.rst b/docs/cookbook/tasks/split_tasks.rst index d16c50e94..96f639037 100644 --- a/docs/cookbook/tasks/split_tasks.rst +++ b/docs/cookbook/tasks/split_tasks.rst @@ -62,8 +62,8 @@ How Do Splits Influence Dates And Dates Influence Splits - In the case of a shorter duration splits, starting with the latest ones, will be either removed or shortened until the new duration is met. -Examples -======== +Examples for splitting Tasks +============================ Throughout the following examples, each successive one will build on the previous. start_date, due_date and duration being ignored diff --git a/docs/cookbook/tasks/updating_tasks.rst b/docs/cookbook/tasks/updating_tasks.rst index c7c216e3f..db2433e79 100644 --- a/docs/cookbook/tasks/updating_tasks.rst +++ b/docs/cookbook/tasks/updating_tasks.rst @@ -32,9 +32,9 @@ General Rules first, then ``due_date`` (otherwise setting ``duration`` will change ``due_date`` after it is set). -******** -Examples -******** +*************************** +Examples for updating Tasks +*************************** The following examples show what the resulting Task object will look like after being run on the initial Task object listed under the header of each section. diff --git a/docs/reference.rst b/docs/reference.rst index 77241f052..5f3888a52 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -42,7 +42,6 @@ The documentation for all of the methods you'll need in your scripts lives in he Shotgun.close Shotgun.authenticate_human_user Shotgun.get_session_token - Shotgun.set_up_auth_cookie Shotgun.add_user_agent Shotgun.reset_user_agent Shotgun.set_session_uuid From a1de54efe3880afc3e5ab1aa67b37fb36b741e69 Mon Sep 17 00:00:00 2001 From: Carlos Villavicencio <123113322+carlos-villavicencio-adsk@users.noreply.github.com> Date: Thu, 22 May 2025 12:17:35 -0500 Subject: [PATCH 089/125] Packaging for v3.8.3 (#384) --- HISTORY.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/HISTORY.rst b/HISTORY.rst index ad4ebda7a..10e34bb55 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -4,6 +4,17 @@ Flow Production Tracking Python API Changelog Here you can see the full list of changes between each Python API release. +v3.8.3 (2025 May 22) +==================== + +- Add improvements to Mockgun. + Ensure string comparison are case insensitive. + Ignore duplicate entities when ``multi_entity_update_mode`` is added. + Support for ``None`` in mockgun when using ordering. + Thank you rlessardrodeofx, slingshotsys, and MHendricks for your contributions. +- Minor fixes on unit tests and documentation. + + v3.8.2 (2025 Mar 11) ==================== From cfab2b48fc4df378ca7219adbf99b70d96d3f983 Mon Sep 17 00:00:00 2001 From: Julien Langlois <16244608+julien-lang@users.noreply.github.com> Date: Tue, 3 Jun 2025 07:08:22 -0700 Subject: [PATCH 090/125] Better CI Job Name (#388) --- azure-pipelines-templates/run-tests.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/azure-pipelines-templates/run-tests.yml b/azure-pipelines-templates/run-tests.yml index bc402c42c..6c60b39c8 100644 --- a/azure-pipelines-templates/run-tests.yml +++ b/azure-pipelines-templates/run-tests.yml @@ -33,9 +33,9 @@ parameters: jobs: # The job will be named after the OS and Azure will suffix the strategy to make it unique - # so we'll have a job name "Windows Python27" for example. What's a strategy? Strategies are the - # name of the keys under the strategy.matrix scope. So for each OS we'll have " Python27" and - # " Python37". + # so we'll have a job name "Windows Python 2.7" for example. What's a strategy? Strategies are the + # name of the keys under the strategy.matrix scope. So for each OS we'll have " Python 2.7" and + # " Python 3.7". - job: ${{ parameters.name }} pool: vmImage: ${{ parameters.vm_image }} @@ -44,11 +44,11 @@ jobs: strategy: matrix: # We support these versions of Python. - Python39: + Python 3.9: python.version: '3.9' - Python310: + Python 3.10: python.version: '3.10' - Python311: + Python 3.11: python.version: '3.11' maxParallel: 4 From 209949404121add444ecd3776c19ba1085d84935 Mon Sep 17 00:00:00 2001 From: Carlos Villavicencio <123113322+carlos-villavicencio-adsk@users.noreply.github.com> Date: Tue, 10 Jun 2025 15:25:00 -0500 Subject: [PATCH 091/125] SG-37924 Replace `utcfromtimestamp` for Python 3.12 (#390) * Replace `utcfromtimestamp` for Python 3.12 * Support expected value --- tests/test_client.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/test_client.py b/tests/test_client.py index e29c6158d..4a81996f1 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -587,7 +587,12 @@ def test_transform_data(self): now = datetime.datetime.fromtimestamp(timestamp).replace( microsecond=0, tzinfo=SG_TIMEZONE.local ) - utc_now = datetime.datetime.utcfromtimestamp(timestamp).replace(microsecond=0) + utc_now = ( + datetime.datetime.fromtimestamp(timestamp, tz=datetime.timezone.utc) + .replace(microsecond=0) + .astimezone(None) + .replace(tzinfo=None) + ) local = {"date": now.strftime("%Y-%m-%d"), "datetime": now, "time": now.time()} # date will still be the local date, because they are not transformed utc = { From 7b392bde6ec033e4a1c5749117fc47214e246380 Mon Sep 17 00:00:00 2001 From: Carlos Villavicencio <123113322+carlos-villavicencio-adsk@users.noreply.github.com> Date: Wed, 11 Jun 2025 12:10:10 -0500 Subject: [PATCH 092/125] Packaging for v3.8.4 (#391) --- HISTORY.rst | 5 +++++ setup.py | 2 +- shotgun_api3/shotgun.py | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 10e34bb55..60e978958 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -4,6 +4,11 @@ Flow Production Tracking Python API Changelog Here you can see the full list of changes between each Python API release. +v3.8.4 (2025 Jun 11) +==================== + +- Replace ``utcfromtimestamp`` to prevent breaking changes in Python 3.12. + v3.8.3 (2025 May 22) ==================== diff --git a/setup.py b/setup.py index cf3304f91..9240486b5 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ setup( name="shotgun_api3", - version="3.8.2", + version="3.8.4", description="Flow Production Tracking Python API", long_description=readme, author="Autodesk", diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index b682e87eb..fea25deba 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -122,7 +122,7 @@ def _is_mimetypes_broken(): # ---------------------------------------------------------------------------- # Version -__version__ = "3.8.2" +__version__ = "3.8.4" # ---------------------------------------------------------------------------- # Errors From bd4615cd00389110168fcfc155e5612f658fe954 Mon Sep 17 00:00:00 2001 From: Julien Langlois <16244608+julien-lang@users.noreply.github.com> Date: Mon, 14 Jul 2025 04:45:05 -0700 Subject: [PATCH 093/125] SG-4373 Improve documentation about local file references (#389) * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Carlos Villavicencio <123113322+carlos-villavicencio-adsk@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Carlos Villavicencio <123113322+carlos-villavicencio-adsk@users.noreply.github.com> --- docs/cookbook/attachments.rst | 105 ++++++++++++++++++++++++++++------ docs/reference.rst | 7 ++- 2 files changed, 92 insertions(+), 20 deletions(-) diff --git a/docs/cookbook/attachments.rst b/docs/cookbook/attachments.rst index e265071e8..934ed58e8 100644 --- a/docs/cookbook/attachments.rst +++ b/docs/cookbook/attachments.rst @@ -238,7 +238,7 @@ are available: A dictionary representing which LocalStorage entity is applied for this local file link. - **url** (:obj:`str`) *read-only*: - A file:// link provided for convenience pointing to the value in the ``local_path`` + A file URI (``file://``) path provided for convenience pointing to the value in the ``local_path`` Reading Local File Fields ========================= @@ -284,40 +284,107 @@ defaults. Any other keys that are provided will be ignored. Optionally set the mime-type of the associated local file. This is assigned automatically using a best-guess based on the file extension. - * **name** :obj:`str`: Optional display name of the local file. This is set to the filename by default. * **local_path** :obj:`str`: - The full local path to the file. Flow Production Tracking will find the LocalStorage + The full local path to the file. Flow Production Tracking will find the ``LocalStorage`` that has the most specific match to this path and automatically assign that LocalStorage to the file. + Alternative to ``relative_path`` + +* **local_storage** :obj:`dict`: + The reference to an existing ``LocalStorage``. + Must contain ``type: LocalStorage`` plus either an ``id`` or a ``name`` + +* **relative_path** :obj:`str`: + The path to the file relative ``local_storage`` root. + Requires ``local_storage`` + Only accepting slash ``/`` separated path. Does not accept Windows path. + Alternative to ``local_path`` + +Example 1: Using ``local_path`` +------------------------------ :: - data = {'sg_uploaded_movie': {'local_path': '/Users/kp/Movies/testing/test_movie_002.mov', - 'name': 'Better Movie'} - result = sg.update('Version', 123, data) + result = sg.update( + 'Version', + 123, + { + 'sg_uploaded_movie': { + 'local_path': '/Users/kp/Movies/testing/test_movie_002.mov', + 'name': 'Better Movie', + } + ) Returns:: - {'id':123, - 'sg_uploaded_movie': { 'content_type': 'video/quicktime', - 'link_type': 'local', - 'name': 'my_test_movie.mov', - 'local_path': '/Users/kp/Movies/testing/test_movie_002.mov' - 'local_path_linux': '/home/users/macusers/kp/Movies/testing/test_movie_002.mov' - 'local_path_mac': '/Users/kp/Movies/testing/test_movie_002.mov' - 'local_path_windows': 'M:\\macusers\kp\Movies\testing\test_movie_002.mov' - 'local_storage': {'id': 1, - 'name': 'Dailies Directories', - 'type': 'LocalStorage'}, - 'url': 'file:///Users/kp/Movies/testing/test_movie_002.mov'}, - 'type': 'Version'}] + { + 'id':123, + 'sg_uploaded_movie': { + 'content_type': 'video/quicktime', + 'link_type': 'local', + 'name': 'my_test_movie.mov', + 'local_path': '/Users/kp/Movies/testing/test_movie_002.mov' + 'local_path_linux': '/home/users/macusers/kp/Movies/testing/test_movie_002.mov' + 'local_path_mac': '/Users/kp/Movies/testing/test_movie_002.mov' + 'local_path_windows': 'M:\\macusers\kp\Movies\testing\test_movie_002.mov' + 'local_storage': { + 'id': 1, + 'name': 'Dailies Directories', + 'type': 'LocalStorage' + }, + 'url': 'file:///Users/kp/Movies/testing/test_movie_002.mov' + }, + 'type': 'Version', + } The ``content_type`` was assigned a best-guess value based on the file extension. Flow Production Tracking selected the most appropriate specific LocalStorage match and assigned it to local_storage automatically. + +Example 2: Using ``relative_path`` +--------------------------------- + +:: + + result = sg.update( + 'Version', + 123, + { + 'sg_uploaded_movie': { + 'local_storage': { + 'type': 'LocalStorage', + 'name': 'Dailies Directories', + }, + 'relative_path': 'testing/test_movie_002.mov', + } + ) + +Returns:: + + { + 'id':123, + 'sg_uploaded_movie': { + 'content_type': 'video/quicktime', + 'link_type': 'local', + 'name': 'my_test_movie.mov', + 'local_path': '/Users/kp/Movies/testing/test_movie_002.mov', + 'local_path_linux': '/home/users/macusers/kp/Movies/testing/test_movie_002.mov', + 'local_path_mac': '/Users/kp/Movies/testing/test_movie_002.mov', + 'local_path_windows': 'M:\\macusers\kp\Movies\testing\test_movie_002.mov', + 'local_storage': { + 'id': 1, + 'name': 'Dailies Directories', + 'type': 'LocalStorage' + }, + 'url': 'file:///Users/kp/Movies/testing/test_movie_002.mov' + }, + 'type': 'Version', + } + + Un-setting local file field values ================================== diff --git a/docs/reference.rst b/docs/reference.rst index 5f3888a52..e2e050e86 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -803,8 +803,13 @@ Additional keys exist for local file links 'local_path_linux': "string" | None, 'local_path_mac': "string" | None, 'local_path_windows': "string" | None, - 'local_storage': {dictionary}, + 'local_storage': { + 'type': 'LocalStorage', + 'id': int | None, + 'name': "string" | None, + }, 'name': "string", + 'relative_path': "string" | None 'url': "string", } API versions < v3.0.3: From 17c66c4191e4a4210e2fa0c7bb30536326927ec2 Mon Sep 17 00:00:00 2001 From: Carlos Villavicencio <123113322+carlos-villavicencio-adsk@users.noreply.github.com> Date: Thu, 17 Jul 2025 14:57:51 -0500 Subject: [PATCH 094/125] SG-38213 Prevent unexpected retries on error (#379) * Prevent unexpected retries on error * Fix tests * Update shotgun_api3/shotgun.py Co-authored-by: Julien Langlois <16244608+julien-lang@users.noreply.github.com> * Packaging for v3.8.3-beta1 * Fix typo * Update HISTORY.rst Co-authored-by: Julien Langlois <16244608+julien-lang@users.noreply.github.com> --------- Co-authored-by: Julien Langlois <16244608+julien-lang@users.noreply.github.com> --- HISTORY.rst | 7 +++++++ shotgun_api3/shotgun.py | 7 +++---- tests/test_api.py | 36 +----------------------------------- tests/test_client.py | 10 +--------- 4 files changed, 12 insertions(+), 48 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 60e978958..c19b61fec 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -4,6 +4,13 @@ Flow Production Tracking Python API Changelog Here you can see the full list of changes between each Python API release. +v3.8.5 (2025 Xxx X) +=================== + +- We don't want to retry on general exceptions (e.g. timeout or remote disconnection) + because we might send a resource modification request (create, batch create, etc) and + we can end up duplicating things. + v3.8.4 (2025 Jun 11) ==================== diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index fea25deba..98e38d83b 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -3938,11 +3938,10 @@ def _make_call(self, verb, path, body, headers): if attempt == max_rpc_attempts: LOG.debug("Request failed. Giving up after %d attempts." % attempt) raise - except Exception: + except Exception as e: self._close_connection() - if attempt == max_rpc_attempts: - LOG.debug("Request failed. Giving up after %d attempts." % attempt) - raise + LOG.debug(f"Request failed. Reason: {e}", exc_info=True) + raise LOG.debug( "Request failed, attempt %d of %d. Retrying in %.2f seconds..." diff --git a/tests/test_api.py b/tests/test_api.py index 788c9751b..407df92fd 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -2223,42 +2223,8 @@ def test_make_call_retry(self, mock_request): self.assertEqual(cm2.exception.args[0], "not working") log_content = "\n".join(cm1.output) - for i in [1, 2]: - self.assertIn( - f"Request failed, attempt {i} of 3. Retrying", - log_content, - ) - self.assertIn( - "Request failed. Giving up after 3 attempts.", - log_content, - ) - - # Then, make the exception happening only once and prove the - # retry works - def my_side_effect(*args, **kwargs): - try: - if my_side_effect.counter < 1: - raise Exception("not working") - - return mock.DEFAULT - finally: - my_side_effect.counter += 1 - - my_side_effect.counter = 0 - mock_request.side_effect = my_side_effect - with self.assertLogs("shotgun_api3", level="DEBUG") as cm: - self.assertIsInstance( - self.sg.info(), - dict, - ) - - log_content = "\n".join(cm.output) self.assertIn( - "Request failed, attempt 1 of 3. Retrying", - log_content, - ) - self.assertNotIn( - "Request failed, attempt 2 of 3. Retrying", + "Request failed. Reason: not working", log_content, ) diff --git a/tests/test_client.py b/tests/test_client.py index 4a81996f1..ea6892137 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -330,17 +330,9 @@ def test_network_retry(self): with mock.patch("time.sleep") as mock_sleep: self.assertRaises(httplib2.HttpLib2Error, self.sg.info) self.assertTrue( - self.sg.config.max_rpc_attempts == self.sg._http_request.call_count, + self.sg._http_request.call_count == 1, "Call is repeated", ) - # Ensure that sleep was called with the retry interval between each attempt - attempt_interval = self.sg.config.rpc_attempt_interval / 1000.0 - calls = [mock.callargs(((attempt_interval,), {}))] - calls *= self.sg.config.max_rpc_attempts - 1 - self.assertTrue( - mock_sleep.call_args_list == calls, - "Call is repeated at correct interval.", - ) def test_set_retry_interval(self): """Setting the retry interval through parameter and environment variable works.""" From f3fa9bc8c9dc9a316301c952a820deeeecce7e3a Mon Sep 17 00:00:00 2001 From: Julien Langlois <16244608+julien-lang@users.noreply.github.com> Date: Fri, 18 Jul 2025 01:21:22 -0700 Subject: [PATCH 095/125] SG-38877 Update certifi third party library to version2025.7.14 (#397) * Bump certifi to version 2025.7.9 * Fixup file EOL * Update to 2025.07.14 --- shotgun_api3/lib/certifi/__init__.py | 2 +- shotgun_api3/lib/certifi/cacert.pem | 448 +++++++++++---------------- shotgun_api3/lib/certifi/core.py | 33 +- shotgun_api3/lib/requirements.txt | 4 +- 4 files changed, 189 insertions(+), 298 deletions(-) diff --git a/shotgun_api3/lib/certifi/__init__.py b/shotgun_api3/lib/certifi/__init__.py index ee8686bec..e8370493f 100644 --- a/shotgun_api3/lib/certifi/__init__.py +++ b/shotgun_api3/lib/certifi/__init__.py @@ -1,4 +1,4 @@ from .core import contents, where __all__ = ["contents", "where"] -__version__ = "2024.12.14" +__version__ = "2025.07.14" diff --git a/shotgun_api3/lib/certifi/cacert.pem b/shotgun_api3/lib/certifi/cacert.pem index ef509f865..64c05d7f3 100644 --- a/shotgun_api3/lib/certifi/cacert.pem +++ b/shotgun_api3/lib/certifi/cacert.pem @@ -1,95 +1,4 @@ -# Issuer: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA -# Subject: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA -# Label: "GlobalSign Root CA" -# Serial: 4835703278459707669005204 -# MD5 Fingerprint: 3e:45:52:15:09:51:92:e1:b7:5d:37:9f:b1:87:29:8a -# SHA1 Fingerprint: b1:bc:96:8b:d4:f4:9d:62:2a:a8:9a:81:f2:15:01:52:a4:1d:82:9c -# SHA256 Fingerprint: eb:d4:10:40:e4:bb:3e:c7:42:c9:e3:81:d3:1e:f2:a4:1a:48:b6:68:5c:96:e7:ce:f3:c1:df:6c:d4:33:1c:99 ------BEGIN CERTIFICATE----- -MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG -A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv -b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw -MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i -YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT -aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ -jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp -xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp -1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG -snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ -U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8 -9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E -BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B -AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz -yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE -38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP -AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad -DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME -HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== ------END CERTIFICATE----- - -# Issuer: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited -# Subject: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited -# Label: "Entrust.net Premium 2048 Secure Server CA" -# Serial: 946069240 -# MD5 Fingerprint: ee:29:31:bc:32:7e:9a:e6:e8:b5:f7:51:b4:34:71:90 -# SHA1 Fingerprint: 50:30:06:09:1d:97:d4:f5:ae:39:f7:cb:e7:92:7d:7d:65:2d:34:31 -# SHA256 Fingerprint: 6d:c4:71:72:e0:1c:bc:b0:bf:62:58:0d:89:5f:e2:b8:ac:9a:d4:f8:73:80:1e:0c:10:b9:c8:37:d2:1e:b1:77 ------BEGIN CERTIFICATE----- -MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML -RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp -bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5 -IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRp -ZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQxNzUwNTFaFw0yOTA3 -MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3 -LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxp -YWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEG -A1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArU1LqRKGsuqjIAcVFmQq -K0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOLGp18EzoOH1u3Hs/lJBQe -sYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSrhRSGlVuX -MlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVT -XTzWnLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/ -HoZdenoVve8AjhUiVBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH -4QIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV -HQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJKoZIhvcNAQEFBQADggEBADub -j1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPyT/4xmf3IDExo -U8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf -zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5b -u/8j72gZyxKTJ1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+ -bYQLCIt+jerXmCHG8+c8eS9enNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/Er -fF6adulZkMV8gzURZVE= ------END CERTIFICATE----- - -# Issuer: CN=Baltimore CyberTrust Root O=Baltimore OU=CyberTrust -# Subject: CN=Baltimore CyberTrust Root O=Baltimore OU=CyberTrust -# Label: "Baltimore CyberTrust Root" -# Serial: 33554617 -# MD5 Fingerprint: ac:b6:94:a5:9c:17:e0:d7:91:52:9b:b1:97:06:a6:e4 -# SHA1 Fingerprint: d4:de:20:d0:5e:66:fc:53:fe:1a:50:88:2c:78:db:28:52:ca:e4:74 -# SHA256 Fingerprint: 16:af:57:a9:f6:76:b0:ab:12:60:95:aa:5e:ba:de:f2:2a:b3:11:19:d6:44:ac:95:cd:4b:93:db:f3:f2:6a:eb ------BEGIN CERTIFICATE----- -MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ -RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD -VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX -DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y -ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy -VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr -mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr -IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK -mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu -XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy -dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye -jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1 -BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3 -DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92 -9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx -jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0 -Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz -ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS -R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp ------END CERTIFICATE----- - # 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" @@ -125,39 +34,6 @@ eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0tHuu2guQOHXvgR1m 0vdXcDazv/wor3ElhVsT/h5/WrQ8 -----END CERTIFICATE----- -# Issuer: CN=AAA Certificate Services O=Comodo CA Limited -# Subject: CN=AAA Certificate Services O=Comodo CA Limited -# Label: "Comodo AAA Services root" -# Serial: 1 -# MD5 Fingerprint: 49:79:04:b0:eb:87:19:ac:47:b0:bc:11:51:9b:74:d0 -# SHA1 Fingerprint: d1:eb:23:a4:6d:17:d6:8f:d9:25:64:c2:f1:f1:60:17:64:d8:e3:49 -# SHA256 Fingerprint: d7:a7:a0:fb:5d:7e:27:31:d7:71:e9:48:4e:bc:de:f7:1d:5f:0c:3e:0a:29:48:78:2b:c8:3e:e0:ea:69:9e:f4 ------BEGIN CERTIFICATE----- -MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb -MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow -GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj -YXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL -MAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE -BwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM -GEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP -ADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua -BtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe -3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4 -YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR -rOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm -ez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU -oBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF -MAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v -QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t -b2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF -AAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q -GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz -Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2 -G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi -l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3 -smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== ------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" @@ -245,103 +121,6 @@ mJlglFwjz1onl14LBQaTNx47aTbrqZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK 4SVhM7JZG+Ju1zdXtg2pEto= -----END CERTIFICATE----- -# Issuer: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com -# Subject: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com -# Label: "XRamp Global CA Root" -# Serial: 107108908803651509692980124233745014957 -# MD5 Fingerprint: a1:0b:44:b3:ca:10:d8:00:6e:9d:0f:d8:0f:92:0a:d1 -# SHA1 Fingerprint: b8:01:86:d1:eb:9c:86:a5:41:04:cf:30:54:f3:4c:52:b7:e5:58:c6 -# SHA256 Fingerprint: ce:cd:dc:90:50:99:d8:da:df:c5:b1:d2:09:b7:37:cb:e2:c1:8c:fb:2c:10:c0:ff:0b:cf:0d:32:86:fc:1a:a2 ------BEGIN CERTIFICATE----- -MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCB -gjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEk -MCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRY -UmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQxMTAxMTcx -NDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3 -dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2Vy -dmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB -dXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS6 -38eMpSe2OAtp87ZOqCwuIR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCP -KZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMxfoArtYzAQDsRhtDLooY2YKTVMIJt2W7Q -DxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FEzG+gSqmUsE3a56k0enI4 -qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqsAxcZZPRa -JSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNVi -PvryxS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0P -BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASs -jVy16bYbMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0 -eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQEwDQYJKoZIhvcNAQEFBQAD -ggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc/Kh4ZzXxHfAR -vbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt -qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLa -IR9NmXmd4c8nnxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSy -i6mx5O+aGtA9aZnuqCij4Tyz8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQ -O+7ETPTsJ3xCwnR8gooJybQDJbw= ------END CERTIFICATE----- - -# Issuer: O=The Go Daddy Group, Inc. OU=Go Daddy Class 2 Certification Authority -# Subject: O=The Go Daddy Group, Inc. OU=Go Daddy Class 2 Certification Authority -# Label: "Go Daddy Class 2 CA" -# Serial: 0 -# MD5 Fingerprint: 91:de:06:25:ab:da:fd:32:17:0c:bb:25:17:2a:84:67 -# SHA1 Fingerprint: 27:96:ba:e6:3f:18:01:e2:77:26:1b:a0:d7:77:70:02:8f:20:ee:e4 -# SHA256 Fingerprint: c3:84:6b:f2:4b:9e:93:ca:64:27:4c:0e:c6:7c:1e:cc:5e:02:4f:fc:ac:d2:d7:40:19:35:0e:81:fe:54:6a:e4 ------BEGIN CERTIFICATE----- -MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh -MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE -YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3 -MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo -ZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg -MiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN -ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA -PVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w -wdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi -EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY -avx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+ -YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE -sNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h -/t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5 -IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj -YXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD -ggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy -OO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P -TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ -HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER -dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf -ReYNnyicsbkqWletNw+vHX/bvZ8= ------END CERTIFICATE----- - -# Issuer: O=Starfield Technologies, Inc. OU=Starfield Class 2 Certification Authority -# Subject: O=Starfield Technologies, Inc. OU=Starfield Class 2 Certification Authority -# Label: "Starfield Class 2 CA" -# Serial: 0 -# MD5 Fingerprint: 32:4a:4b:bb:c8:63:69:9b:be:74:9a:c6:dd:1d:46:24 -# SHA1 Fingerprint: ad:7e:1c:28:b0:64:ef:8f:60:03:40:20:14:c3:d0:e3:37:0e:b5:8a -# SHA256 Fingerprint: 14:65:fa:20:53:97:b8:76:fa:a6:f0:a9:95:8e:55:90:e4:0f:cc:7f:aa:4f:b7:c2:c8:67:75:21:fb:5f:b6:58 ------BEGIN CERTIFICATE----- -MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl -MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp -U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw -NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE -ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp -ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3 -DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf -8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN -+lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0 -X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa -K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA -1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G -A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR -zt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0 -YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD -bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w -DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3 -L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D -eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl -xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp -VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY -WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q= ------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" @@ -474,47 +253,6 @@ ZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6LqjviOvrv1vA+ACOzB2+htt Qc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ -----END CERTIFICATE----- -# Issuer: CN=SwissSign Silver CA - G2 O=SwissSign AG -# Subject: CN=SwissSign Silver CA - G2 O=SwissSign AG -# Label: "SwissSign Silver CA - G2" -# Serial: 5700383053117599563 -# MD5 Fingerprint: e0:06:a1:c9:7d:cf:c9:fc:0d:c0:56:75:96:d8:62:13 -# SHA1 Fingerprint: 9b:aa:e5:9f:56:ee:21:cb:43:5a:be:25:93:df:a7:f0:40:d1:1d:cb -# SHA256 Fingerprint: be:6c:4d:a2:bb:b9:ba:59:b6:f3:93:97:68:37:42:46:c3:c0:05:99:3f:a9:8f:02:0d:1d:ed:be:d4:8a:81:d5 ------BEGIN CERTIFICATE----- -MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UE -BhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWdu -IFNpbHZlciBDQSAtIEcyMB4XDTA2MTAyNTA4MzI0NloXDTM2MTAyNTA4MzI0Nlow -RzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMY -U3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A -MIICCgKCAgEAxPGHf9N4Mfc4yfjDmUO8x/e8N+dOcbpLj6VzHVxumK4DV644N0Mv -Fz0fyM5oEMF4rhkDKxD6LHmD9ui5aLlV8gREpzn5/ASLHvGiTSf5YXu6t+WiE7br -YT7QbNHm+/pe7R20nqA1W6GSy/BJkv6FCgU+5tkL4k+73JU3/JHpMjUi0R86TieF -nbAVlDLaYQ1HTWBCrpJH6INaUFjpiou5XaHc3ZlKHzZnu0jkg7Y360g6rw9njxcH -6ATK72oxh9TAtvmUcXtnZLi2kUpCe2UuMGoM9ZDulebyzYLs2aFK7PayS+VFheZt -eJMELpyCbTapxDFkH4aDCyr0NQp4yVXPQbBH6TCfmb5hqAaEuSh6XzjZG6k4sIN/ -c8HDO0gqgg8hm7jMqDXDhBuDsz6+pJVpATqJAHgE2cn0mRmrVn5bi4Y5FZGkECwJ -MoBgs5PAKrYYC51+jUnyEEp/+dVGLxmSo5mnJqy7jDzmDrxHB9xzUfFwZC8I+bRH -HTBsROopN4WSaGa8gzj+ezku01DwH/teYLappvonQfGbGHLy9YR0SslnxFSuSGTf -jNFusB3hB48IHpmccelM2KX3RxIfdNFRnobzwqIjQAtz20um53MGjMGg6cFZrEb6 -5i/4z3GcRm25xBWNOHkDRUjvxF3XCO6HOSKGsg0PWEP3calILv3q1h8CAwEAAaOB -rDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU -F6DNweRBtjpbO8tFnb0cwpj6hlgwHwYDVR0jBBgwFoAUF6DNweRBtjpbO8tFnb0c -wpj6hlgwRgYDVR0gBD8wPTA7BglghXQBWQEDAQEwLjAsBggrBgEFBQcCARYgaHR0 -cDovL3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIB -AHPGgeAn0i0P4JUw4ppBf1AsX19iYamGamkYDHRJ1l2E6kFSGG9YrVBWIGrGvShp -WJHckRE1qTodvBqlYJ7YH39FkWnZfrt4csEGDyrOj4VwYaygzQu4OSlWhDJOhrs9 -xCrZ1x9y7v5RoSJBsXECYxqCsGKrXlcSH9/L3XWgwF15kIwb4FDm3jH+mHtwX6WQ -2K34ArZv02DdQEsixT2tOnqfGhpHkXkzuoLcMmkDlm4fS/Bx/uNncqCxv1yL5PqZ -IseEuRuNI5c/7SXgz2W79WEE790eslpBIlqhn10s6FvJbakMDHiqYMZWjwFaDGi8 -aRl5xB9+lwW/xekkUV7U1UtT7dkjWjYDZaPBA61BMPNGG4WQr2W11bHkFlt4dR2X -em1ZqSqPe97Dh4kQmUlzeMg9vVE1dCrV8X5pGyq7O70luJpaPXJhkGaH7gzWTdQR -dAtq/gsD/KNVV4n+SsuuWxcFyPKNIzFTONItaj+CuY0IavdeQXRuwxF+B6wpYJE/ -OMpXEA29MC/HpeZBoNquBYeaoKRlbEwJDIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY+ -hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ubDgEj8Z+7fNzcbBGXJbLy -tGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u ------END CERTIFICATE----- - # Issuer: CN=SecureTrust CA O=SecureTrust Corporation # Subject: CN=SecureTrust CA O=SecureTrust Corporation # Label: "SecureTrust CA" @@ -4853,4 +4591,188 @@ Af8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTrQciu/NWeUUj1vYv0hyCTQSvT 9DAKBggqhkjOPQQDAwNoADBlAjEA2S6Jfl5OpBEHvVnCB96rMjhTKkZEBhd6zlHp 4P9mLQlO4E/0BdGF9jVg3PVys0Z9AjBEmEYagoUeYWmJSwdLZrWeqrqgHkHZAXQ6 bkU6iYAZezKYVWOr62Nuk22rGwlgMU4= ------END CERTIFICATE----- \ No newline at end of file +-----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/shotgun_api3/lib/certifi/core.py b/shotgun_api3/lib/certifi/core.py index 91f538bb1..1c9661cc7 100644 --- a/shotgun_api3/lib/certifi/core.py +++ b/shotgun_api3/lib/certifi/core.py @@ -46,7 +46,7 @@ def where() -> str: def contents() -> str: return files("certifi").joinpath("cacert.pem").read_text(encoding="ascii") -elif sys.version_info >= (3, 7): +else: from importlib.resources import path as get_path, read_text @@ -81,34 +81,3 @@ def where() -> str: def contents() -> str: return read_text("certifi", "cacert.pem", encoding="ascii") - -else: - import os - import types - from typing import Union - - Package = Union[types.ModuleType, str] - Resource = Union[str, "os.PathLike"] - - # This fallback will work for Python versions prior to 3.7 that lack the - # importlib.resources module but relies on the existing `where` function - # so won't address issues with environments like PyOxidizer that don't set - # __file__ on modules. - def read_text( - package: Package, - resource: Resource, - encoding: str = 'utf-8', - errors: str = 'strict' - ) -> str: - with open(where(), encoding=encoding) as data: - return data.read() - - # If we don't have importlib.resources, then we will just do the old logic - # of assuming we're on the filesystem and munge the path directly. - def where() -> str: - f = os.path.dirname(__file__) - - return os.path.join(f, "cacert.pem") - - def contents() -> str: - return read_text("certifi", "cacert.pem", encoding="ascii") diff --git a/shotgun_api3/lib/requirements.txt b/shotgun_api3/lib/requirements.txt index 34172e948..f91a3ae19 100644 --- a/shotgun_api3/lib/requirements.txt +++ b/shotgun_api3/lib/requirements.txt @@ -30,5 +30,5 @@ # released for our dependencies. httplib2==0.22.0 six==1.13.0 -certifi==2024.7.4 -pyparsing==2.4.7 \ No newline at end of file +certifi==2025.7.14 +pyparsing==2.4.7 From 8738890b5108b5157e2d1a42e61de2215235b0fd Mon Sep 17 00:00:00 2001 From: Julien Langlois <16244608+julien-lang@users.noreply.github.com> Date: Tue, 9 Sep 2025 08:24:51 -0700 Subject: [PATCH 096/125] Fixup minor issues with README (#410) * Fixup badge * Move developer/maintainer part of docs in developer/ folder * Better badges * Update developer/README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 49 ++++----------------------------------------- developer/README.md | 44 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 45 deletions(-) create mode 100644 developer/README.md diff --git a/README.md b/README.md index 4c9e464df..33e493821 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ -[![VFX Platform](https://img.shields.io/badge/vfxplatform-2025%20%7C%202024%20%7C%202023%20%7C%202022-blue.svg)](http://www.vfxplatform.com/) -[![Python](https://img.shields.io/badge/python-3.11%20%7C%203.10%20%7C%203.9-blue.svg)](https://www.python.org/)/) -[![Reference Documentation](http://img.shields.io/badge/doc-reference-blue.svg)](http://developer.shotgridsoftware.com/python-api) +[![Supported VFX Platform: 2022 - 2025](https://img.shields.io/badge/VFX_Platform-2022_|_2023_|_2024_|_2025-blue)](http://www.vfxplatform.com/ "Supported VFX Platform") +[![Supported Python versions: 3.9 - 3.11](https://img.shields.io/badge/Python-3.9_|_3.10_|_3.11-blue?logo=python&logoColor=f5f5f5)](https://www.python.org/ "Supported Python versions") +[![Reference Documentation](http://img.shields.io/badge/Reference-documentation-blue.svg?logo=wikibooks&logoColor=f5f5f5)](http://developer.shotgridsoftware.com/python-api) + [![Build Status](https://dev.azure.com/shotgun-ecosystem/Python%20API/_apis/build/status/shotgunsoftware.python-api?branchName=master)](https://dev.azure.com/shotgun-ecosystem/Python%20API/_build/latest?definitionId=108&branchName=master) [![Coverage Status](https://coveralls.io/repos/github/shotgunsoftware/python-api/badge.svg?branch=master)](https://coveralls.io/github/shotgunsoftware/python-api?branch=master) @@ -25,13 +26,6 @@ Some useful direct links: You can see the [full history of the Python API on the documentation site](http://developer.shotgridsoftware.com/python-api/changelog.html). -## Updating HTTPLib2 - -The API comes with a copy of the `httplib2` inside the `shotgun_api3/lib` folder. To update the copy to a more recent version of the API, you can run the `update_httplib2.py` script at the root of this repository like this: - - python update_httplib2.py vX.Y.Z - -where `vX.Y.Z` is a release found on `httplib2`'s [release page](https://github.com/httplib2/httplib2/releases). ## Tests @@ -47,38 +41,3 @@ Integration and unit tests are provided. - `test_client` and `tests_unit` use mock server interaction and do not require a Flow Production Tracking instance to be available (no modifications to `tests/config` are necessary). - `test_api` and `test_api_long` *do* require a Flow Production Tracking instance, with a script key available for the tests. The server and script user values must be supplied in the `tests/config` file. The tests will add test data to your server based on information in your config. This data will be manipulated by the tests, and should not be used for other purposes. - To run all of the tests, use the shell script `run-tests`. - -## Release process - -### Packaging up new release - -1) Update the Changelog in the `HISTORY.rst` file - - Add bullet points for any changes that have happened since the previous release. This may include changes you did not make so look at the commit history and make sure we don't miss anything. If you notice something was done that wasn't added to the changelog, hunt down that engineer and make them feel guilty for not doing so. This is a required step in making changes to the API. - - Try and match the language of previous change log messages. We want to keep a consistent voice. - - Make sure the date of the release matches today. We try and keep this TBD until we're ready to do a release so it's easy to catch that it needs to be updated. - - Make sure the version number is filled out and correct. We follow semantic versioning. -2) Ensure any changes or additions to public methods are documented - - Ensure that doc strings are updated in the code itself to work with Sphinx and are correctly formatted. - - Examples are always good especially if this a new feature or method. - - Think about a new user to the API trying to figure out how to use the features you're documenting. -3) Update the version value in `python-api/setup.py` to match the version you are packaging. This controls what version users will get when installing via pip. -4) Update the `__version__` value in `shotgun_api3/shotgun.py` to the version you're releasing. This identified the current version within the API itself. -5) Commit these changes in master with a commit message like `packaging for the vx.x.x release`. -6) Create a tag based off of the master branch called `vx.x.x` to match the version number you're releasing. -7) Push master and your tag to Github. -8) Update the Releases page with your new release. - - The release should already be there from your tag but if not, create a new one. - - Add more detailed information regarding the changes in this release. This is a great place to add examples, and reasons for the change! - -### Letting the world know -Post a message in the [Pipeline Community channel](https://community.shotgridsoftware.com/c/pipeline). - -### Prepare for the Next Dev Cycle -1) Update the `__version__` value in `shotgun_api3/shotgun.py` to the next version number with `.dev` appended to it. For example, `v3.0.24.dev` -2) Add a new section to the Changelog in the `HISTORY.rst` file with the next version number and a TBD date -``` - **v3.0.24 - TBD** - + TBD -``` -3) Commit the changes to master with a commit message like `Bump version to v3.0.24.dev` -4) Push master to Github diff --git a/developer/README.md b/developer/README.md new file mode 100644 index 000000000..369070655 --- /dev/null +++ b/developer/README.md @@ -0,0 +1,44 @@ + +# Updating HTTPLib2 + +The API comes with a copy of the `httplib2` inside the `shotgun_api3/lib` folder. To update the copy to a more recent version of the API, you can run the `update_httplib2.py` script at the root of this repository like this: + + python update_httplib2.py vX.Y.Z + +where `vX.Y.Z` is a release found on `httplib2`'s [release page](https://github.com/httplib2/httplib2/releases). + + +# Release process + +## Packaging up new release + +1) Update the Changelog in the `HISTORY.rst` file + - Add bullet points for any changes that have happened since the previous release. This may include changes you did not make so look at the commit history and make sure we don't miss anything. If you notice something was done that wasn't added to the changelog, hunt down that engineer and make them feel guilty for not doing so. This is a required step in making changes to the API. + - Try and match the language of previous change log messages. We want to keep a consistent voice. + - Make sure the date of the release matches today. We try and keep this TBD until we're ready to do a release so it's easy to catch that it needs to be updated. + - Make sure the version number is filled out and correct. We follow semantic versioning. +2) Ensure any changes or additions to public methods are documented + - Ensure that doc strings are updated in the code itself to work with Sphinx and are correctly formatted. + - Examples are always good especially if this a new feature or method. + - Think about a new user to the API trying to figure out how to use the features you're documenting. +3) Update the version value in `python-api/setup.py` to match the version you are packaging. This controls what version users will get when installing via pip. +4) Update the `__version__` value in `shotgun_api3/shotgun.py` to the version you're releasing. This identifies the current version within the API itself. +5) Commit these changes in master with a commit message like `packaging for the vx.x.x release`. +6) Create a tag based off of the master branch called `vx.x.x` to match the version number you're releasing. +7) Push master and your tag to Github. +8) Update the Releases page with your new release. + - The release should already be there from your tag but if not, create a new one. + - Add more detailed information regarding the changes in this release. This is a great place to add examples, and reasons for the change! + +## Letting the world know +Post a message in the [Pipeline Community channel](https://community.shotgridsoftware.com/c/pipeline). + +## Prepare for the Next Dev Cycle +1) Update the `__version__` value in `shotgun_api3/shotgun.py` to the next version number with `.dev` appended to it. For example, `v3.0.24.dev` +2) Add a new section to the Changelog in the `HISTORY.rst` file with the next version number and a TBD date +``` + **v3.0.24 - TBD** + + TBD +``` +3) Commit the changes to master with a commit message like `Bump version to v3.0.24.dev` +4) Push master to Github From efd4d4d61bf9d8c601e024bc0aa6bf6537749b48 Mon Sep 17 00:00:00 2001 From: Julien Langlois <16244608+julien-lang@users.noreply.github.com> Date: Wed, 10 Sep 2025 06:01:48 -0700 Subject: [PATCH 097/125] Import order (#398) --- shotgun_api3/shotgun.py | 24 ++++++++++++------------ tests/test_api.py | 18 ++++++++++-------- tests/test_client.py | 8 ++++---- tests/test_unit.py | 1 + 4 files changed, 27 insertions(+), 24 deletions(-) diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index 98e38d83b..cc80320ba 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -29,27 +29,27 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ -# Python 2/3 compatibility -from .lib import six -from .lib import sgsix -from .lib import sgutils -from .lib.six import BytesIO # used for attachment upload -from .lib.six.moves import map - -from .lib.six.moves import http_cookiejar # used for attachment upload +import copy import datetime +import json import logging -import uuid # used for attachment upload import os import re -import copy +import shutil # used for attachment download import ssl import stat # used for attachment upload import sys import time -import json +import uuid # used for attachment upload + +# Python 2/3 compatibility +from .lib import six +from .lib import sgsix +from .lib import sgutils +from .lib.six import BytesIO # used for attachment upload +from .lib.six.moves import map +from .lib.six.moves import http_cookiejar # used for attachment upload from .lib.six.moves import urllib -import shutil # used for attachment download from .lib.six.moves import http_client # Used for secure file upload. from .lib.httplib2 import Http, ProxyInfo, socks, ssl_error_classes from .lib.sgtimezone import SgTimezone diff --git a/tests/test_api.py b/tests/test_api.py index 407df92fd..99fd0c080 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -16,29 +16,31 @@ from __future__ import print_function import datetime -import sys +import glob import os -from . import mock -from .mock import patch, MagicMock import ssl +import sys import time import types -import uuid import unittest -from shotgun_api3.lib.six.moves import range, urllib +import uuid import warnings -import glob -import shotgun_api3 -from shotgun_api3.lib.httplib2 import Http from shotgun_api3.lib import six +from shotgun_api3.lib.httplib2 import Http # To mock the correct exception when testion on Python 2 and 3, use the # ShotgunSSLError variable from sgsix that contains the appropriate exception # class for the current Python version. from shotgun_api3.lib.sgsix import ShotgunSSLError +from shotgun_api3.lib.six.moves import range, urllib + +import shotgun_api3 + from . import base +from . import mock +from .mock import patch, MagicMock class TestShotgunApi(base.LiveTestBase): diff --git a/tests/test_client.py b/tests/test_client.py index ea6892137..08037ac5e 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -14,7 +14,11 @@ import datetime import os +import platform import re +import sys +import time +import unittest from shotgun_api3.lib.six.moves import urllib from shotgun_api3.lib import six, sgutils @@ -27,10 +31,6 @@ except ImportError: import shotgun_api3.lib.simplejson as json -import platform -import sys -import time -import unittest from . import mock import shotgun_api3.lib.httplib2 as httplib2 diff --git a/tests/test_unit.py b/tests/test_unit.py index de996c553..ff78253c2 100644 --- a/tests/test_unit.py +++ b/tests/test_unit.py @@ -13,6 +13,7 @@ import os import unittest from unittest import mock + from .mock import patch import shotgun_api3 as api from shotgun_api3.shotgun import _is_mimetypes_broken From d3809cd4a8aa3fbfb4b2caabaf75a38d6cfd14cf Mon Sep 17 00:00:00 2001 From: Julien Langlois <16244608+julien-lang@users.noreply.github.com> Date: Wed, 10 Sep 2025 06:19:32 -0700 Subject: [PATCH 098/125] SG-38306 Python2 Removal - Part 2 - Easy ones (#399) * Import order * Remove __future__ imports * Cleanup super prototype Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Eduardo Chauca Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/reference.rst | 3 -- shotgun_api3/shotgun.py | 84 ++++++++++++++++++++++------------------- tests/base.py | 8 ++-- tests/test_api.py | 39 +++++++++---------- tests/test_api_long.py | 3 +- tests/test_client.py | 16 ++++---- tests/test_mockgun.py | 4 +- tests/test_proxy.py | 2 +- 8 files changed, 78 insertions(+), 81 deletions(-) diff --git a/docs/reference.rst b/docs/reference.rst index e2e050e86..96c917469 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -1034,6 +1034,3 @@ Example for a user whose language preference is set to Japanese: }, ... } - -.. note:: - If needed, the encoding of the returned localized string can be ensured regardless the Python version using shotgun_api3.lib.six.ensure_text(). diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index cc80320ba..a97e44c64 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -712,9 +712,9 @@ def __init__( # the lowercase version of the credentials. auth, self.config.server = self._split_url(base_url) if auth: - auth = base64encode( - sgutils.ensure_binary(urllib.parse.unquote(auth)) - ).decode("utf-8") + auth = base64encode(urllib.parse.unquote(auth).encode("utf-8")).decode( + "utf-8" + ) self.config.authorization = "Basic " + auth.strip() # foo:bar@123.456.789.012:3456 @@ -2270,8 +2270,7 @@ def schema_field_update( "type": entity_type, "field_name": field_name, "properties": [ - {"property_name": k, "value": v} - for k, v in six.iteritems((properties or {})) + {"property_name": k, "value": v} for k, v in (properties or {}).items() ], } params = self._add_project_param(params, project_entity) @@ -2966,7 +2965,11 @@ def download_attachment(self, attachment=False, file_path=None, attachment_id=No url.find("s3.amazonaws.com") != -1 and e.headers["content-type"] == "application/xml" ): - body = [sgutils.ensure_text(line) for line in e.readlines()] + body = [ + line.decode("utf-8") if isinstance(line, bytes) else line + for line in e.readlines() + ] + if body: xml = "".join(body) # Once python 2.4 support is not needed we can think about using @@ -3328,7 +3331,7 @@ def text_search(self, text, entity_types, project_ids=None, limit=None): raise ValueError("entity_types parameter must be a dictionary") api_entity_types = {} - for entity_type, filter_list in six.iteritems(entity_types): + for entity_type, filter_list in entity_types.items(): if isinstance(filter_list, (list, tuple)): resolved_filters = _translate_filters(filter_list, filter_operator=None) @@ -3859,8 +3862,7 @@ def _encode_payload(self, payload): be in a single byte encoding to go over the wire. """ - wire = json.dumps(payload, ensure_ascii=False) - return sgutils.ensure_binary(wire) + return json.dumps(payload, ensure_ascii=False).encode("utf-8") def _make_call(self, verb, path, body, headers): """ @@ -3964,7 +3966,7 @@ def _http_request(self, verb, path, body, headers): resp, content = conn.request(url, method=verb, body=body, headers=headers) # http response code is handled else where http_status = (resp.status, resp.reason) - resp_headers = dict((k.lower(), v) for k, v in six.iteritems(resp)) + resp_headers = dict((k.lower(), v) for k, v in resp.items()) resp_body = content LOG.debug("Response status is %s %s" % http_status) @@ -4044,7 +4046,7 @@ def _decode_list(lst): def _decode_dict(dct): newdict = {} - for k, v in six.iteritems(dct): + for k, v in dct.items(): if isinstance(k, str): k = sgutils.ensure_str(k) if isinstance(v, str): @@ -4118,7 +4120,7 @@ def _visit_data(self, data, visitor): return tuple(recursive(i, visitor) for i in data) if isinstance(data, dict): - return dict((k, recursive(v, visitor)) for k, v in six.iteritems(data)) + return dict((k, recursive(v, visitor)) for k, v in data.items()) return visitor(data) @@ -4165,10 +4167,6 @@ def _outbound_visitor(value): value = _change_tz(value) return value.strftime("%Y-%m-%dT%H:%M:%SZ") - # ensure return is six.text_type - if isinstance(value, str): - return sgutils.ensure_text(value) - return value return self._visit_data(data, _outbound_visitor) @@ -4287,7 +4285,7 @@ def _parse_records(self, records): continue # iterate over each item and check each field for possible injection - for k, v in six.iteritems(rec): + for k, v in rec.items(): if not v: continue @@ -4375,7 +4373,7 @@ def _dict_to_list( [{'field_name': 'foo', 'value': 'bar', 'thing1': 'value1'}] """ ret = [] - for k, v in six.iteritems((d or {})): + for k, v in (d or {}).items(): d = {key_name: k, value_name: v} d.update((extra_data or {}).get(k, {})) ret.append(d) @@ -4388,7 +4386,7 @@ def _dict_to_extra_data(self, d, key_name="value"): e.g. d {'foo' : 'bar'} changed to {'foo': {"value": 'bar'}] """ - return dict([(k, {key_name: v}) for (k, v) in six.iteritems((d or {}))]) + return dict([(k, {key_name: v}) for (k, v) in (d or {}).items()]) def _upload_file_to_storage(self, path, storage_url): """ @@ -4656,7 +4654,10 @@ def _send_form(self, url, params): else: raise ShotgunError("Unanticipated error occurred %s" % (e)) - return sgutils.ensure_text(result) + if isinstance(result, bytes): + result = result.decode("utf-8") + + return result else: raise ShotgunError("Max attemps limit reached.") @@ -4737,9 +4738,8 @@ def http_request(self, request): else: params.append((key, value)) if not files: - data = sgutils.ensure_binary( - urllib.parse.urlencode(params, True) - ) # sequencing on + data = urllib.parse.urlencode(params, True).encode("utf-8") + # sequencing on else: boundary, data = self.encode(params, files) content_type = "multipart/form-data; boundary=%s" % boundary @@ -4762,42 +4762,48 @@ def encode(self, params, files, boundary=None, buffer=None): if buffer is None: buffer = BytesIO() for key, value in params: - if not isinstance(value, str): + if isinstance(key, bytes): + key = key.decode("utf-8") + + if isinstance(value, bytes): + value = value.decode("utf-8") + elif not isinstance(value, str): # If value is not a string (e.g. int) cast to text value = str(value) - value = sgutils.ensure_text(value) - key = sgutils.ensure_text(key) - buffer.write(sgutils.ensure_binary("--%s\r\n" % boundary)) + buffer.write(f"--{boundary}\r\n".encode("utf-8")) buffer.write( - sgutils.ensure_binary('Content-Disposition: form-data; name="%s"' % key) + f'Content-Disposition: form-data; name="{key}"'.encode("utf-8") ) - buffer.write(sgutils.ensure_binary("\r\n\r\n%s\r\n" % value)) + buffer.write(f"\r\n\r\n{value}\r\n".encode("utf-8")) for key, fd in files: # On Windows, it's possible that we were forced to open a file # with non-ascii characters as unicode. In that case, we need to # encode it as a utf-8 string to remove unicode from the equation. # If we don't, the mix of unicode and strings going into the # buffer can cause UnicodeEncodeErrors to be raised. - filename = fd.name - filename = sgutils.ensure_text(filename) + filename = ( + fd.name.decode("utf-8") if isinstance(fd.name, bytes) else fd.name + ) filename = filename.split("/")[-1] - key = sgutils.ensure_text(key) + if isinstance(key, bytes): + key = key.decode("utf-8") + content_type = mimetypes.guess_type(filename)[0] content_type = content_type or "application/octet-stream" file_size = os.fstat(fd.fileno())[stat.ST_SIZE] - buffer.write(sgutils.ensure_binary("--%s\r\n" % boundary)) + buffer.write(f"--{boundary}\r\n".encode("utf-8")) c_dis = 'Content-Disposition: form-data; name="%s"; filename="%s"%s' content_disposition = c_dis % (key, filename, "\r\n") - buffer.write(sgutils.ensure_binary(content_disposition)) - buffer.write(sgutils.ensure_binary("Content-Type: %s\r\n" % content_type)) - buffer.write(sgutils.ensure_binary("Content-Length: %s\r\n" % file_size)) + buffer.write(content_disposition.encode("utf-8")) + buffer.write(f"Content-Type: {content_type}\r\n".encode("utf-8")) + buffer.write(f"Content-Length: {file_size}\r\n".encode("utf-8")) - buffer.write(sgutils.ensure_binary("\r\n")) + buffer.write(b"\r\n") fd.seek(0) shutil.copyfileobj(fd, buffer) - buffer.write(sgutils.ensure_binary("\r\n")) - buffer.write(sgutils.ensure_binary("--%s--\r\n\r\n" % boundary)) + buffer.write(b"\r\n") + buffer.write(f"--{boundary}--\r\n\r\n".encode("utf-8")) buffer = buffer.getvalue() return boundary, buffer diff --git a/tests/base.py b/tests/base.py index 2820d495d..cc8634996 100644 --- a/tests/base.py +++ b/tests/base.py @@ -135,7 +135,7 @@ class MockTestBase(TestBase): """Test base for tests mocking server interactions.""" def setUp(self): - super(MockTestBase, self).setUp() + super().setUp() # TODO see if there is another way to stop sg connecting self._setup_mock() self._setup_mock_data() @@ -252,7 +252,7 @@ class LiveTestBase(TestBase): def setUp(self, auth_mode=None): if not auth_mode: auth_mode = "HumanUser" if self.config.jenkins else "ApiUser" - super(LiveTestBase, self).setUp(auth_mode) + super().setUp(auth_mode) if ( self.sg.server_caps.version and self.sg.server_caps.version >= (3, 3, 0) @@ -410,7 +410,7 @@ class HumanUserAuthLiveTestBase(LiveTestBase): """ def setUp(self): - super(HumanUserAuthLiveTestBase, self).setUp("HumanUser") + super().setUp("HumanUser") class SessionTokenAuthLiveTestBase(LiveTestBase): @@ -420,7 +420,7 @@ class SessionTokenAuthLiveTestBase(LiveTestBase): """ def setUp(self): - super(SessionTokenAuthLiveTestBase, self).setUp("SessionToken") + super().setUp("SessionToken") class SgTestConfig(object): diff --git a/tests/test_api.py b/tests/test_api.py index 99fd0c080..11cd8e8cd 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -14,7 +14,6 @@ test_api_long for other tests. """ -from __future__ import print_function import datetime import glob import os @@ -45,7 +44,7 @@ class TestShotgunApi(base.LiveTestBase): def setUp(self): - super(TestShotgunApi, self).setUp() + super().setUp() # give note unicode content self.sg.update("Note", self.note["id"], {"content": "La Pe\xf1a"}) @@ -247,9 +246,7 @@ def test_upload_download(self): # test upload of non-ascii, unicode path u_path = os.path.abspath( - os.path.expanduser( - glob.glob(os.path.join(six.text_type(this_dir), "Noëlご.jpg"))[0] - ) + os.path.expanduser(glob.glob(os.path.join(this_dir, "Noëlご.jpg"))[0]) ) # If this is a problem, it'll raise with a UnicodeEncodeError. We @@ -327,9 +324,7 @@ def test_upload_to_sg(self, mock_send_form): mock_send_form.return_value = "1\n:123\nasd" this_dir, _ = os.path.split(__file__) u_path = os.path.abspath( - os.path.expanduser( - glob.glob(os.path.join(six.text_type(this_dir), "Noëlご.jpg"))[0] - ) + os.path.expanduser(glob.glob(os.path.join(this_dir, "Noëlご.jpg"))[0]) ) upload_id = self.sg.upload( "Version", @@ -419,7 +414,7 @@ def test_upload_thumbnail_in_create(self): url = new_version.get("filmstrip_image") data = self.sg.download_attachment({"url": url}) - self.assertTrue(isinstance(data, six.binary_type)) + self.assertTrue(isinstance(data, bytes)) self.sg.delete("Version", new_version["id"]) @@ -1061,7 +1056,7 @@ class TestDataTypes(base.LiveTestBase): """ def setUp(self): - super(TestDataTypes, self).setUp() + super().setUp() def test_set_checkbox(self): entity = "HumanUser" @@ -1271,7 +1266,7 @@ class TestUtc(base.LiveTestBase): """Test utc options""" def setUp(self): - super(TestUtc, self).setUp() + super().setUp() utc = shotgun_api3.shotgun.SG_TIMEZONE.utc self.datetime_utc = datetime.datetime(2008, 10, 13, 23, 10, tzinfo=utc) local = shotgun_api3.shotgun.SG_TIMEZONE.local @@ -1313,7 +1308,7 @@ def _assert_expected(self, sg, date_time, expected): class TestFind(base.LiveTestBase): def setUp(self): - super(TestFind, self).setUp() + super().setUp() # We will need the created_at field for the shot fields = list(self.shot.keys())[:] fields.append("created_at") @@ -2109,7 +2104,7 @@ def test_following(self): class TestErrors(base.TestBase): def setUp(self): auth_mode = "HumanUser" if self.config.jenkins else "ApiUser" - super(TestErrors, self).setUp(auth_mode) + super().setUp(auth_mode) def test_bad_auth(self): """test_bad_auth invalid script name or api key raises fault""" @@ -2401,7 +2396,7 @@ def test_upload_missing_file(self): class TestScriptUserSudoAuth(base.LiveTestBase): def setUp(self): - super(TestScriptUserSudoAuth, self).setUp() + super().setUp() self.sg.update( "HumanUser", @@ -2442,7 +2437,7 @@ def test_user_is_creator(self): class TestHumanUserSudoAuth(base.TestBase): def setUp(self): - super(TestHumanUserSudoAuth, self).setUp("HumanUser") + super().setUp("HumanUser") def test_human_user_sudo_auth_fails(self): """ @@ -2713,7 +2708,7 @@ class TestActivityStream(base.LiveTestBase): """ def setUp(self): - super(TestActivityStream, self).setUp() + super().setUp() self._prefix = uuid.uuid4().hex self._shot = self.sg.create( @@ -2763,7 +2758,7 @@ def tearDown(self): ) self.sg.batch(batch_data) - super(TestActivityStream, self).tearDown() + super().tearDown() def test_simple(self): """ @@ -2836,7 +2831,7 @@ class TestNoteThreadRead(base.LiveTestBase): """ def setUp(self): - super(TestNoteThreadRead, self).setUp() + super().setUp() # get path to our std attahcment this_dir, _ = os.path.split(__file__) @@ -3047,7 +3042,7 @@ class TestTextSearch(base.LiveTestBase): """ def setUp(self): - super(TestTextSearch, self).setUp() + super().setUp() # create 5 shots and 5 assets to search for self._prefix = uuid.uuid4().hex @@ -3087,7 +3082,7 @@ def tearDown(self): ) self.sg.batch(batch_data) - super(TestTextSearch, self).tearDown() + super().tearDown() def test_simple(self): """ @@ -3471,9 +3466,9 @@ def test_import_httplib(self): def _has_unicode(data): for k, v in data.items(): - if isinstance(k, six.text_type): + if isinstance(k, str): return True - if isinstance(v, six.text_type): + if isinstance(v, str): return True return False diff --git a/tests/test_api_long.py b/tests/test_api_long.py index 0bf509b3c..29a34e991 100644 --- a/tests/test_api_long.py +++ b/tests/test_api_long.py @@ -13,7 +13,6 @@ Includes the schema functions and the automated searching for all entity types """ -from __future__ import print_function from . import base import random import shotgun_api3 @@ -56,7 +55,7 @@ def test_automated_find(self): # pivot_column fields aren't valid for sorting so ensure we're # not using one. order_field = None - for field_name, field in six.iteritems(fields): + for field_name, field in fields.items(): # Restrict sorting to only types we know will always be sortable # Since no_sorting is not exposed to us, we'll have to rely on # this as a safeguard against trying to sort by a field with diff --git a/tests/test_client.py b/tests/test_client.py index 08037ac5e..9c826322e 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -45,14 +45,17 @@ def b64encode(val): - return base64encode(sgutils.ensure_binary(val)).decode("utf-8") + if isinstance(val, str): + val = val.encode("utf-8") + + return base64encode(val).decode("utf-8") class TestShotgunClient(base.MockTestBase): """Test case for shotgun api with server interactions mocked.""" def setUp(self): - super(TestShotgunClient, self).setUp() + super().setUp() # get domain and uri scheme match = re.search("(https?://)(.*)", self.server_url) self.uri_prefix = match.group(1) @@ -422,11 +425,10 @@ def test_call_rpc(self): expected = "rpc response with list result, first item" self.assertEqual(d["results"][0], rv, expected) - # Test unicode mixed with utf-8 as reported in Ticket #17959 + # Test payload encoding with non-ascii characters (using utf-8 literal) d = {"results": ["foo", "bar"]} a = { - "utf_str": "\xe2\x88\x9a", - "unicode_str": sgutils.ensure_text("\xe2\x88\x9a"), + "utf_literal": "\xe2\x88\x9a", } self._mock_http(d) rv = self.sg._call_rpc("list", a) @@ -640,9 +642,7 @@ def test_encode_payload(self): self.assertTrue(isinstance(j, bytes)) def test_decode_response_ascii(self): - self._assert_decode_resonse( - True, sgutils.ensure_str("my data \u00e0", encoding="utf8") - ) + self._assert_decode_resonse(True, "my data \u00e0") def test_decode_response_unicode(self): self._assert_decode_resonse(False, "my data \u00e0") diff --git a/tests/test_mockgun.py b/tests/test_mockgun.py index e7e4295e4..ad478304a 100644 --- a/tests/test_mockgun.py +++ b/tests/test_mockgun.py @@ -79,7 +79,7 @@ def setUp(self): """ Creates test data. """ - super(TestValidateFilterSyntax, self).setUp() + super().setUp() self._mockgun = Mockgun( "https://test.shotgunstudio.com", login="user", password="1234" @@ -578,7 +578,7 @@ def setUp(self): """ Creates tests data. """ - super(TestFilterOperator, self).setUp() + super().setUp() self._mockgun = Mockgun( "https://test.shotgunstudio.com", login="user", password="1234" diff --git a/tests/test_proxy.py b/tests/test_proxy.py index cb713cd9d..7bf0d7006 100644 --- a/tests/test_proxy.py +++ b/tests/test_proxy.py @@ -18,7 +18,7 @@ class ServerConnectionTest(base.TestBase): """Tests for server connection""" def setUp(self): - super(ServerConnectionTest, self).setUp() + super().setUp() def test_connection(self): """Tests server connects and returns nothing""" From 517f65f0540f39f5d007ed352524678691f98ab2 Mon Sep 17 00:00:00 2001 From: Julien Langlois <16244608+julien-lang@users.noreply.github.com> Date: Wed, 10 Sep 2025 07:08:22 -0700 Subject: [PATCH 099/125] SG-38306 Python2 Removal - Part 3 - Cleanup imports (#400) * six.moves imports * Cleanup BytesIO import from six * simple json * Cleanup Py2-3 compat with ImportError * Simplify Base64 --------- Co-authored-by: Eduardo Chauca --- docs/cookbook/examples/ami_handler.rst | 2 +- shotgun_api3/lib/mockgun/schema.py | 2 +- shotgun_api3/shotgun.py | 45 ++++++++++++-------------- tests/base.py | 19 +++-------- tests/test_api.py | 11 ++++--- tests/test_client.py | 23 ++++--------- tests/test_unit.py | 3 +- 7 files changed, 42 insertions(+), 63 deletions(-) diff --git a/docs/cookbook/examples/ami_handler.rst b/docs/cookbook/examples/ami_handler.rst index 3fb5e3571..6b8f3384b 100644 --- a/docs/cookbook/examples/ami_handler.rst +++ b/docs/cookbook/examples/ami_handler.rst @@ -218,7 +218,7 @@ via ``POST``. If you're using a custom protocol the data is sent via ``GET``. params = params.split("&") p = {"column_display_names": [], "cols": []} for arg in params: - key, value = map(six.moves.urllib.parse.unquote, arg.split("=", 1)) + key, value = map(urllib.parse.unquote, arg.split("=", 1)) if key == "column_display_names" or key == "cols": p[key].append(value) else: diff --git a/shotgun_api3/lib/mockgun/schema.py b/shotgun_api3/lib/mockgun/schema.py index 5d5019df4..ab671629d 100644 --- a/shotgun_api3/lib/mockgun/schema.py +++ b/shotgun_api3/lib/mockgun/schema.py @@ -30,8 +30,8 @@ ----------------------------------------------------------------------------- """ -from ..six.moves import cPickle as pickle import os +import pickle from .errors import MockgunError diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index a97e44c64..3a2c9b8eb 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -29,9 +29,13 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ +import base64 import copy import datetime import json +import http.client # Used for secure file upload +import http.cookiejar # used for attachment upload +import io # used for attachment upload import logging import os import re @@ -40,29 +44,22 @@ import stat # used for attachment upload import sys import time +import urllib.error +import urllib.parse +import urllib.request import uuid # used for attachment upload +# Import Error and ResponseError (even though they're unused in this file) since they need +# to be exposed as part of the API. +from xmlrpc.client import Error, ProtocolError, ResponseError # noqa + # Python 2/3 compatibility from .lib import six from .lib import sgsix from .lib import sgutils -from .lib.six import BytesIO # used for attachment upload -from .lib.six.moves import map -from .lib.six.moves import http_cookiejar # used for attachment upload -from .lib.six.moves import urllib -from .lib.six.moves import http_client # Used for secure file upload. from .lib.httplib2 import Http, ProxyInfo, socks, ssl_error_classes from .lib.sgtimezone import SgTimezone -# Import Error and ResponseError (even though they're unused in this file) since they need -# to be exposed as part of the API. -from .lib.six.moves.xmlrpc_client import Error, ProtocolError, ResponseError # noqa - -if six.PY3: - from base64 import encodebytes as base64encode -else: - from base64 import encodestring as base64encode - LOG = logging.getLogger("shotgun_api3") """ @@ -708,13 +705,13 @@ def __init__( # and auth header # Do NOT self._split_url(self.base_url) here, as it contains the lower - # case version of the base_url argument. Doing so would base64encode + # case version of the base_url argument. Doing so would base64.encodebytes # the lowercase version of the credentials. auth, self.config.server = self._split_url(base_url) if auth: - auth = base64encode(urllib.parse.unquote(auth).encode("utf-8")).decode( - "utf-8" - ) + auth = base64.encodebytes( + urllib.parse.unquote(auth).encode("utf-8") + ).decode("utf-8") self.config.authorization = "Basic " + auth.strip() # foo:bar@123.456.789.012:3456 @@ -3003,8 +3000,8 @@ def get_auth_cookie_handler(self): This is used internally for downloading attachments from FPTR. """ sid = self.get_session_token() - cj = http_cookiejar.LWPCookieJar() - c = http_cookiejar.Cookie( + cj = http.cookiejar.LWPCookieJar() + c = http.cookiejar.Cookie( "0", "_session_id", sid, @@ -4432,7 +4429,7 @@ def _multipart_upload_file_to_storage(self, path, upload_info): data_size = len(data) # keep data as a stream so that we don't need to worry how it was # encoded. - data = BytesIO(data) + data = io.BytesIO(data) bytes_read += data_size part_url = self._get_upload_part_link( upload_info, filename, part_number @@ -4662,13 +4659,13 @@ def _send_form(self, url, params): raise ShotgunError("Max attemps limit reached.") -class CACertsHTTPSConnection(http_client.HTTPConnection): +class CACertsHTTPSConnection(http.client.HTTPConnection): """ " This class allows to create an HTTPS connection that uses the custom certificates passed in. """ - default_port = http_client.HTTPS_PORT + default_port = http.client.HTTPS_PORT def __init__(self, *args, **kwargs): """ @@ -4760,7 +4757,7 @@ def encode(self, params, files, boundary=None, buffer=None): # We'll do this across both python 2/3 rather than add more branching. boundary = uuid.uuid4() if buffer is None: - buffer = BytesIO() + buffer = io.BytesIO() for key, value in params: if isinstance(key, bytes): key = key.decode("utf-8") diff --git a/tests/base.py b/tests/base.py index cc8634996..e30ec01a4 100644 --- a/tests/base.py +++ b/tests/base.py @@ -1,31 +1,20 @@ """Base class for Flow Production Tracking API tests.""" +import configparser import contextlib +import json import os import random import re import time import unittest +import urllib.error from . import mock import shotgun_api3 as api -from shotgun_api3.shotgun import json from shotgun_api3.shotgun import ServerCapabilities from shotgun_api3.lib import six -from shotgun_api3.lib.six.moves import urllib -from shotgun_api3.lib.six.moves.configparser import ConfigParser - -try: - # Attempt to import skip from unittest. Since this was added in Python 2.7 - # in the case that we're running on Python 2.6 we'll need a decorator to - # provide some equivalent functionality. - from unittest import skip -except ImportError: - # On Python 2.6 we'll just have to ignore tests that are skipped -- we won't - # mark them as skipped, but we will not fail on them. - def skip(f): - return lambda self: None THUMBNAIL_MAX_ATTEMPTS = 30 @@ -456,7 +445,7 @@ def config_keys(self): ] def read_config(self, config_path): - config_parser = ConfigParser() + config_parser = configparser.ConfigParser() config_parser.read(config_path) for section in config_parser.sections(): for option in config_parser.options(section): diff --git a/tests/test_api.py b/tests/test_api.py index 11cd8e8cd..cd4dcbeef 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -22,6 +22,9 @@ import time import types import unittest +import urllib.parse +import urllib.request +import urllib.error import uuid import warnings @@ -33,8 +36,6 @@ # class for the current Python version. from shotgun_api3.lib.sgsix import ShotgunSSLError -from shotgun_api3.lib.six.moves import range, urllib - import shotgun_api3 from . import base @@ -629,7 +630,7 @@ def test_linked_thumbnail_url(self): # For now skip tests that are erroneously failling on some sites to # allow CI to pass until the known issue causing this is resolved. - @base.skip("Skipping test that erroneously fails on some sites.") + @unittest.skip("Skipping test that erroneously fails on some sites.") def test_share_thumbnail(self): """share thumbnail between two entities""" @@ -2899,7 +2900,7 @@ def _check_attachment(self, data, attachment_id, additional_fields): # For now skip tests that are erroneously failling on some sites to # allow CI to pass until the known issue causing this is resolved. - @base.skip("Skipping test that erroneously fails on some sites.") + @unittest.skip("Skipping test that erroneously fails on some sites.") def test_simple(self): """ Test note reply thread API call @@ -2978,7 +2979,7 @@ def test_simple(self): # For now skip tests that are erroneously failling on some sites to # allow CI to pass until the known issue causing this is resolved. - @base.skip("Skipping test that erroneously fails on some sites.") + @unittest.skip("Skipping test that erroneously fails on some sites.") def test_complex(self): """ Test note reply thread API call with additional params diff --git a/tests/test_client.py b/tests/test_client.py index 9c826322e..164731908 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -12,25 +12,21 @@ CRUD functions. These tests always use a mock http connection so not not need a live server to run against.""" +import configparser +import base64 import datetime +import json import os import platform import re import sys import time import unittest +import urllib.parse +import urllib.error -from shotgun_api3.lib.six.moves import urllib from shotgun_api3.lib import six, sgutils -try: - import simplejson as json -except ImportError: - try: - import json as json - except ImportError: - import shotgun_api3.lib.simplejson as json - from . import mock import shotgun_api3.lib.httplib2 as httplib2 @@ -38,17 +34,12 @@ from shotgun_api3.shotgun import ServerCapabilities, SG_TIMEZONE from . import base -if six.PY3: - from base64 import encodebytes as base64encode -else: - from base64 import encodestring as base64encode - def b64encode(val): if isinstance(val, str): val = val.encode("utf-8") - return base64encode(val).decode("utf-8") + return base64.encodebytes(val).decode("utf-8") class TestShotgunClient(base.MockTestBase): @@ -190,7 +181,7 @@ def test_read_config(self): """Validate that config values are properly coerced.""" this_dir = os.path.dirname(os.path.realpath(__file__)) config_path = os.path.join(this_dir, "test_config_file") - config = base.ConfigParser() + config = configparser.ConfigParser() config.read(config_path) result = config.get("SERVER_INFO", "api_key") expected = "%abce" diff --git a/tests/test_unit.py b/tests/test_unit.py index ff78253c2..d2853e5df 100644 --- a/tests/test_unit.py +++ b/tests/test_unit.py @@ -13,11 +13,12 @@ import os import unittest from unittest import mock +import urllib.request +import urllib.error from .mock import patch import shotgun_api3 as api from shotgun_api3.shotgun import _is_mimetypes_broken -from shotgun_api3.lib.six.moves import range, urllib from shotgun_api3.lib.httplib2 import Http, ssl_error_classes From 8c5ef90ea9fa39440974d672788133889fc614c7 Mon Sep 17 00:00:00 2001 From: Julien Langlois <16244608+julien-lang@users.noreply.github.com> Date: Wed, 10 Sep 2025 07:54:24 -0700 Subject: [PATCH 100/125] SG-38306 Python2 Removal - Part 4 - Mimetypes module (#401) * Remove deprecated custom mimetype module --------- Co-authored-by: Eduardo Chauca --- shotgun_api3/lib/README.md | 13 - shotgun_api3/lib/mimetypes.py | 598 ---------------------------------- shotgun_api3/shotgun.py | 29 +- tests/test_unit.py | 19 -- 4 files changed, 1 insertion(+), 658 deletions(-) delete mode 100644 shotgun_api3/lib/mimetypes.py diff --git a/shotgun_api3/lib/README.md b/shotgun_api3/lib/README.md index 1bdec6f78..7097b6c62 100644 --- a/shotgun_api3/lib/README.md +++ b/shotgun_api3/lib/README.md @@ -10,19 +10,6 @@ Some third-party modules are bundled with `python-api` inside lib. The version of `httplib2` bundled should be updated manually, however its version is included in the unused `shotgun_api3/lib/requirements.txt` to allow Github's automated CVE notifications to work. -### mimetypes - -The `mimetypes` module is broken on Windows only for Python 2.7.0 to 2.7.9 inclusively. -We bundle the version from 2.7.10 - -See bugs: - - * [9291](http://bugs.python.org/issue9291) (Fixed in 2.7.7) - * [21652](http://bugs.python.org/issue21652) (Fixed in 2.7.8) - * [22028](http://bugs.python.org/issue22028) (Fixed in 2.7.10) - -The version of `mimetypes` bundled should be updated manually if necessary, however it is unlikely this will be needed, as it is only used for Python versions 2.7.0 - 2.7.9, and newer Python versions simply use the native `mimetypes` module. - ### six Six is a Python 2/3 compatibility library. In python-api, it's used to make simultaneous support for Python on 2 and 3 easier to maintain and more readable, but allowing the use of common helper functions, unified interfaces for modules that changed, and variables to ease type comparisons. For more on six, see the [documentation](https://six.readthedocs.io/). diff --git a/shotgun_api3/lib/mimetypes.py b/shotgun_api3/lib/mimetypes.py deleted file mode 100644 index bc8488535..000000000 --- a/shotgun_api3/lib/mimetypes.py +++ /dev/null @@ -1,598 +0,0 @@ -"""Guess the MIME type of a file. - -This module defines two useful functions: - -guess_type(url, strict=1) -- guess the MIME type and encoding of a URL. - -guess_extension(type, strict=1) -- guess the extension for a given MIME type. - -It also contains the following, for tuning the behavior: - -Data: - -knownfiles -- list of files to parse -inited -- flag set when init() has been called -suffix_map -- dictionary mapping suffixes to suffixes -encodings_map -- dictionary mapping suffixes to encodings -types_map -- dictionary mapping suffixes to types - -Functions: - -init([files]) -- parse a list of files, default knownfiles (on Windows, the - default values are taken from the registry) -read_mime_types(file) -- parse one file, return a dictionary or None - -Note that this code has not been updated for python 3 compatibility, as it is -a patched version of the native mimetypes module and is used only in Python -versions 2.7.0 - 2.7.9, which included a broken version of the mimetypes module. -""" - -import os -import sys -import posixpath -import urllib -try: - import _winreg -except ImportError: - _winreg = None - -__all__ = [ - "guess_type","guess_extension","guess_all_extensions", - "add_type","read_mime_types","init" -] - -knownfiles = [ - "/etc/mime.types", - "/etc/httpd/mime.types", # Mac OS X - "/etc/httpd/conf/mime.types", # Apache - "/etc/apache/mime.types", # Apache 1 - "/etc/apache2/mime.types", # Apache 2 - "/usr/local/etc/httpd/conf/mime.types", - "/usr/local/lib/netscape/mime.types", - "/usr/local/etc/httpd/conf/mime.types", # Apache 1.2 - "/usr/local/etc/mime.types", # Apache 1.3 - ] - -inited = False -_db = None - - -class MimeTypes: - """MIME-types datastore. - - This datastore can handle information from mime.types-style files - and supports basic determination of MIME type from a filename or - URL, and can guess a reasonable extension given a MIME type. - """ - - def __init__(self, filenames=(), strict=True): - if not inited: - init() - self.encodings_map = encodings_map.copy() - self.suffix_map = suffix_map.copy() - self.types_map = ({}, {}) # dict for (non-strict, strict) - self.types_map_inv = ({}, {}) - for (ext, type) in types_map.items(): - self.add_type(type, ext, True) - for (ext, type) in common_types.items(): - self.add_type(type, ext, False) - for name in filenames: - self.read(name, strict) - - def add_type(self, type, ext, strict=True): - """Add a mapping between a type and an extension. - - When the extension is already known, the new - type will replace the old one. When the type - is already known the extension will be added - to the list of known extensions. - - If strict is true, information will be added to - list of standard types, else to the list of non-standard - types. - """ - self.types_map[strict][ext] = type - exts = self.types_map_inv[strict].setdefault(type, []) - if ext not in exts: - exts.append(ext) - - def guess_type(self, url, strict=True): - """Guess the type of a file based on its URL. - - Return value is a tuple (type, encoding) where type is None if - the type can't be guessed (no or unknown suffix) or a string - of the form type/subtype, usable for a MIME Content-type - header; and encoding is None for no encoding or the name of - the program used to encode (e.g. compress or gzip). The - mappings are table driven. Encoding suffixes are case - sensitive; type suffixes are first tried case sensitive, then - case insensitive. - - The suffixes .tgz, .taz and .tz (case sensitive!) are all - mapped to '.tar.gz'. (This is table-driven too, using the - dictionary suffix_map.) - - Optional `strict' argument when False adds a bunch of commonly found, - but non-standard types. - """ - scheme, url = urllib.splittype(url) - if scheme == 'data': - # syntax of data URLs: - # dataurl := "data:" [ mediatype ] [ ";base64" ] "," data - # mediatype := [ type "/" subtype ] *( ";" parameter ) - # data := *urlchar - # parameter := attribute "=" value - # type/subtype defaults to "text/plain" - comma = url.find(',') - if comma < 0: - # bad data URL - return None, None - semi = url.find(';', 0, comma) - if semi >= 0: - type = url[:semi] - else: - type = url[:comma] - if '=' in type or '/' not in type: - type = 'text/plain' - return type, None # never compressed, so encoding is None - base, ext = posixpath.splitext(url) - while ext in self.suffix_map: - base, ext = posixpath.splitext(base + self.suffix_map[ext]) - if ext in self.encodings_map: - encoding = self.encodings_map[ext] - base, ext = posixpath.splitext(base) - else: - encoding = None - types_map = self.types_map[True] - if ext in types_map: - return types_map[ext], encoding - elif ext.lower() in types_map: - return types_map[ext.lower()], encoding - elif strict: - return None, encoding - types_map = self.types_map[False] - if ext in types_map: - return types_map[ext], encoding - elif ext.lower() in types_map: - return types_map[ext.lower()], encoding - else: - return None, encoding - - def guess_all_extensions(self, type, strict=True): - """Guess the extensions for a file based on its MIME type. - - Return value is a list of strings giving the possible filename - extensions, including the leading dot ('.'). The extension is not - guaranteed to have been associated with any particular data stream, - but would be mapped to the MIME type `type' by guess_type(). - - Optional `strict' argument when false adds a bunch of commonly found, - but non-standard types. - """ - type = type.lower() - extensions = self.types_map_inv[True].get(type, []) - if not strict: - for ext in self.types_map_inv[False].get(type, []): - if ext not in extensions: - extensions.append(ext) - return extensions - - def guess_extension(self, type, strict=True): - """Guess the extension for a file based on its MIME type. - - Return value is a string giving a filename extension, - including the leading dot ('.'). The extension is not - guaranteed to have been associated with any particular data - stream, but would be mapped to the MIME type `type' by - guess_type(). If no extension can be guessed for `type', None - is returned. - - Optional `strict' argument when false adds a bunch of commonly found, - but non-standard types. - """ - extensions = self.guess_all_extensions(type, strict) - if not extensions: - return None - return extensions[0] - - def read(self, filename, strict=True): - """ - Read a single mime.types-format file, specified by pathname. - - If strict is true, information will be added to - list of standard types, else to the list of non-standard - types. - """ - with open(filename) as fp: - self.readfp(fp, strict) - - def readfp(self, fp, strict=True): - """ - Read a single mime.types-format file. - - If strict is true, information will be added to - list of standard types, else to the list of non-standard - types. - """ - while 1: - line = fp.readline() - if not line: - break - words = line.split() - for i in range(len(words)): - if words[i][0] == '#': - del words[i:] - break - if not words: - continue - type, suffixes = words[0], words[1:] - for suff in suffixes: - self.add_type(type, '.' + suff, strict) - - def read_windows_registry(self, strict=True): - """ - Load the MIME types database from Windows registry. - - If strict is true, information will be added to - list of standard types, else to the list of non-standard - types. - """ - - # Windows only - if not _winreg: - return - - def enum_types(mimedb): - i = 0 - while True: - try: - ctype = _winreg.EnumKey(mimedb, i) - except EnvironmentError: - break - else: - if '\0' not in ctype: - yield ctype - i += 1 - - default_encoding = sys.getdefaultencoding() - with _winreg.OpenKey(_winreg.HKEY_CLASSES_ROOT, '') as hkcr: - for subkeyname in enum_types(hkcr): - try: - with _winreg.OpenKey(hkcr, subkeyname) as subkey: - # Only check file extensions - if not subkeyname.startswith("."): - continue - # raises EnvironmentError if no 'Content Type' value - mimetype, datatype = _winreg.QueryValueEx( - subkey, 'Content Type') - if datatype != _winreg.REG_SZ: - continue - try: - mimetype = mimetype.encode(default_encoding) - except UnicodeEncodeError: - continue - self.add_type(mimetype, subkeyname, strict) - except EnvironmentError: - continue - -def guess_type(url, strict=True): - """Guess the type of a file based on its URL. - - Return value is a tuple (type, encoding) where type is None if the - type can't be guessed (no or unknown suffix) or a string of the - form type/subtype, usable for a MIME Content-type header; and - encoding is None for no encoding or the name of the program used - to encode (e.g. compress or gzip). The mappings are table - driven. Encoding suffixes are case sensitive; type suffixes are - first tried case sensitive, then case insensitive. - - The suffixes .tgz, .taz and .tz (case sensitive!) are all mapped - to ".tar.gz". (This is table-driven too, using the dictionary - suffix_map). - - Optional `strict' argument when false adds a bunch of commonly found, but - non-standard types. - """ - if _db is None: - init() - return _db.guess_type(url, strict) - - -def guess_all_extensions(type, strict=True): - """Guess the extensions for a file based on its MIME type. - - Return value is a list of strings giving the possible filename - extensions, including the leading dot ('.'). The extension is not - guaranteed to have been associated with any particular data - stream, but would be mapped to the MIME type `type' by - guess_type(). If no extension can be guessed for `type', None - is returned. - - Optional `strict' argument when false adds a bunch of commonly found, - but non-standard types. - """ - if _db is None: - init() - return _db.guess_all_extensions(type, strict) - -def guess_extension(type, strict=True): - """Guess the extension for a file based on its MIME type. - - Return value is a string giving a filename extension, including the - leading dot ('.'). The extension is not guaranteed to have been - associated with any particular data stream, but would be mapped to the - MIME type `type' by guess_type(). If no extension can be guessed for - `type', None is returned. - - Optional `strict' argument when false adds a bunch of commonly found, - but non-standard types. - """ - if _db is None: - init() - return _db.guess_extension(type, strict) - -def add_type(type, ext, strict=True): - """Add a mapping between a type and an extension. - - When the extension is already known, the new - type will replace the old one. When the type - is already known the extension will be added - to the list of known extensions. - - If strict is true, information will be added to - list of standard types, else to the list of non-standard - types. - """ - if _db is None: - init() - return _db.add_type(type, ext, strict) - - -def init(files=None): - global suffix_map, types_map, encodings_map, common_types - global inited, _db - inited = True # so that MimeTypes.__init__() doesn't call us again - db = MimeTypes() - if files is None: - if _winreg: - db.read_windows_registry() - files = knownfiles - for file in files: - if os.path.isfile(file): - db.read(file) - encodings_map = db.encodings_map - suffix_map = db.suffix_map - types_map = db.types_map[True] - common_types = db.types_map[False] - # Make the DB a global variable now that it is fully initialized - _db = db - - -def read_mime_types(file): - try: - f = open(file) - except IOError: - return None - with f: - db = MimeTypes() - db.readfp(f, True) - return db.types_map[True] - - -def _default_mime_types(): - global suffix_map - global encodings_map - global types_map - global common_types - - suffix_map = { - '.tgz': '.tar.gz', - '.taz': '.tar.gz', - '.tz': '.tar.gz', - '.tbz2': '.tar.bz2', - '.txz': '.tar.xz', - } - - encodings_map = { - '.gz': 'gzip', - '.Z': 'compress', - '.bz2': 'bzip2', - '.xz': 'xz', - } - - # Before adding new types, make sure they are either registered with IANA, - # at http://www.isi.edu/in-notes/iana/assignments/media-types - # or extensions, i.e. using the x- prefix - - # If you add to these, please keep them sorted! - types_map = { - '.a' : 'application/octet-stream', - '.ai' : 'application/postscript', - '.aif' : 'audio/x-aiff', - '.aifc' : 'audio/x-aiff', - '.aiff' : 'audio/x-aiff', - '.au' : 'audio/basic', - '.avi' : 'video/x-msvideo', - '.bat' : 'text/plain', - '.bcpio' : 'application/x-bcpio', - '.bin' : 'application/octet-stream', - '.bmp' : 'image/x-ms-bmp', - '.c' : 'text/plain', - # Duplicates :( - '.cdf' : 'application/x-cdf', - '.cdf' : 'application/x-netcdf', - '.cpio' : 'application/x-cpio', - '.csh' : 'application/x-csh', - '.css' : 'text/css', - '.dll' : 'application/octet-stream', - '.doc' : 'application/msword', - '.dot' : 'application/msword', - '.dvi' : 'application/x-dvi', - '.eml' : 'message/rfc822', - '.eps' : 'application/postscript', - '.etx' : 'text/x-setext', - '.exe' : 'application/octet-stream', - '.gif' : 'image/gif', - '.gtar' : 'application/x-gtar', - '.h' : 'text/plain', - '.hdf' : 'application/x-hdf', - '.htm' : 'text/html', - '.html' : 'text/html', - '.ico' : 'image/vnd.microsoft.icon', - '.ief' : 'image/ief', - '.jpe' : 'image/jpeg', - '.jpeg' : 'image/jpeg', - '.jpg' : 'image/jpeg', - '.js' : 'application/javascript', - '.ksh' : 'text/plain', - '.latex' : 'application/x-latex', - '.m1v' : 'video/mpeg', - '.man' : 'application/x-troff-man', - '.me' : 'application/x-troff-me', - '.mht' : 'message/rfc822', - '.mhtml' : 'message/rfc822', - '.mif' : 'application/x-mif', - '.mov' : 'video/quicktime', - '.movie' : 'video/x-sgi-movie', - '.mp2' : 'audio/mpeg', - '.mp3' : 'audio/mpeg', - '.mp4' : 'video/mp4', - '.mpa' : 'video/mpeg', - '.mpe' : 'video/mpeg', - '.mpeg' : 'video/mpeg', - '.mpg' : 'video/mpeg', - '.ms' : 'application/x-troff-ms', - '.nc' : 'application/x-netcdf', - '.nws' : 'message/rfc822', - '.o' : 'application/octet-stream', - '.obj' : 'application/octet-stream', - '.oda' : 'application/oda', - '.p12' : 'application/x-pkcs12', - '.p7c' : 'application/pkcs7-mime', - '.pbm' : 'image/x-portable-bitmap', - '.pdf' : 'application/pdf', - '.pfx' : 'application/x-pkcs12', - '.pgm' : 'image/x-portable-graymap', - '.pl' : 'text/plain', - '.png' : 'image/png', - '.pnm' : 'image/x-portable-anymap', - '.pot' : 'application/vnd.ms-powerpoint', - '.ppa' : 'application/vnd.ms-powerpoint', - '.ppm' : 'image/x-portable-pixmap', - '.pps' : 'application/vnd.ms-powerpoint', - '.ppt' : 'application/vnd.ms-powerpoint', - '.ps' : 'application/postscript', - '.pwz' : 'application/vnd.ms-powerpoint', - '.py' : 'text/x-python', - '.pyc' : 'application/x-python-code', - '.pyo' : 'application/x-python-code', - '.qt' : 'video/quicktime', - '.ra' : 'audio/x-pn-realaudio', - '.ram' : 'application/x-pn-realaudio', - '.ras' : 'image/x-cmu-raster', - '.rdf' : 'application/xml', - '.rgb' : 'image/x-rgb', - '.roff' : 'application/x-troff', - '.rtx' : 'text/richtext', - '.sgm' : 'text/x-sgml', - '.sgml' : 'text/x-sgml', - '.sh' : 'application/x-sh', - '.shar' : 'application/x-shar', - '.snd' : 'audio/basic', - '.so' : 'application/octet-stream', - '.src' : 'application/x-wais-source', - '.sv4cpio': 'application/x-sv4cpio', - '.sv4crc' : 'application/x-sv4crc', - '.swf' : 'application/x-shockwave-flash', - '.t' : 'application/x-troff', - '.tar' : 'application/x-tar', - '.tcl' : 'application/x-tcl', - '.tex' : 'application/x-tex', - '.texi' : 'application/x-texinfo', - '.texinfo': 'application/x-texinfo', - '.tif' : 'image/tiff', - '.tiff' : 'image/tiff', - '.tr' : 'application/x-troff', - '.tsv' : 'text/tab-separated-values', - '.txt' : 'text/plain', - '.ustar' : 'application/x-ustar', - '.vcf' : 'text/x-vcard', - '.wav' : 'audio/x-wav', - '.wiz' : 'application/msword', - '.wsdl' : 'application/xml', - '.xbm' : 'image/x-xbitmap', - '.xlb' : 'application/vnd.ms-excel', - # Duplicates :( - '.xls' : 'application/excel', - '.xls' : 'application/vnd.ms-excel', - '.xml' : 'text/xml', - '.xpdl' : 'application/xml', - '.xpm' : 'image/x-xpixmap', - '.xsl' : 'application/xml', - '.xwd' : 'image/x-xwindowdump', - '.zip' : 'application/zip', - } - - # These are non-standard types, commonly found in the wild. They will - # only match if strict=0 flag is given to the API methods. - - # Please sort these too - common_types = { - '.jpg' : 'image/jpg', - '.mid' : 'audio/midi', - '.midi': 'audio/midi', - '.pct' : 'image/pict', - '.pic' : 'image/pict', - '.pict': 'image/pict', - '.rtf' : 'application/rtf', - '.xul' : 'text/xul' - } - - -_default_mime_types() - - -if __name__ == '__main__': - import getopt - - USAGE = """\ -Usage: mimetypes.py [options] type - -Options: - --help / -h -- print this message and exit - --lenient / -l -- additionally search of some common, but non-standard - types. - --extension / -e -- guess extension instead of type - -More than one type argument may be given. -""" - - def usage(code, msg=''): - print USAGE - if msg: print msg - sys.exit(code) - - try: - opts, args = getopt.getopt(sys.argv[1:], 'hle', - ['help', 'lenient', 'extension']) - except getopt.error, msg: - usage(1, msg) - - strict = 1 - extension = 0 - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-l', '--lenient'): - strict = 0 - elif opt in ('-e', '--extension'): - extension = 1 - for gtype in args: - if extension: - guess = guess_extension(gtype, strict) - if not guess: print "I don't know anything about type", gtype - else: print guess - else: - guess, encoding = guess_type(gtype, strict) - if not guess: print "I don't know anything about type", gtype - else: print 'type:', guess, 'encoding:', encoding \ No newline at end of file diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index 3a2c9b8eb..778393309 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -37,6 +37,7 @@ import http.cookiejar # used for attachment upload import io # used for attachment upload import logging +import mimetypes import os import re import shutil # used for attachment download @@ -72,34 +73,6 @@ """ LOG.setLevel(logging.WARN) - -def _is_mimetypes_broken(): - """ - Checks if this version of Python ships with a broken version of mimetypes - - :returns: True if the version of mimetypes is broken, False otherwise. - """ - # mimetypes is broken on Windows only and for Python 2.7.0 to 2.7.9 inclusively. - # We're bundling the version from 2.7.10. - # See bugs : - # http://bugs.python.org/issue9291 <- Fixed in 2.7.7 - # http://bugs.python.org/issue21652 <- Fixed in 2.7.8 - # http://bugs.python.org/issue22028 <- Fixed in 2.7.10 - return ( - sys.platform == "win32" - and sys.version_info[0] == 2 - and sys.version_info[1] == 7 - and sys.version_info[2] >= 0 - and sys.version_info[2] <= 9 - ) - - -if _is_mimetypes_broken(): - from .lib import mimetypes as mimetypes -else: - import mimetypes - - # mimetypes imported in version specific imports mimetypes.add_type("video/webm", ".webm") # webm and mp4 seem to be missing mimetypes.add_type("video/mp4", ".mp4") # from some OS/distros diff --git a/tests/test_unit.py b/tests/test_unit.py index d2853e5df..58e46d366 100644 --- a/tests/test_unit.py +++ b/tests/test_unit.py @@ -18,7 +18,6 @@ from .mock import patch import shotgun_api3 as api -from shotgun_api3.shotgun import _is_mimetypes_broken from shotgun_api3.lib.httplib2 import Http, ssl_error_classes @@ -800,23 +799,5 @@ def test_urlib(self): assert response is not None -class TestMimetypesFix(unittest.TestCase): - """ - Makes sure that the mimetypes fix will be imported. - """ - - @patch("shotgun_api3.shotgun.sys") - def _test_mimetypes_import( - self, platform, major, minor, patch_number, result, mock - ): - """ - Mocks sys.platform and sys.version_info to test the mimetypes import code. - """ - - mock.version_info = [major, minor, patch_number] - mock.platform = platform - self.assertEqual(_is_mimetypes_broken(), result) - - if __name__ == "__main__": unittest.main() From a15e61778e2a2fa8c56420b05c52c05d37dda298 Mon Sep 17 00:00:00 2001 From: Julien Langlois <16244608+julien-lang@users.noreply.github.com> Date: Wed, 10 Sep 2025 10:27:15 -0700 Subject: [PATCH 101/125] SG-38306 Python2 Removal - Part 5 - Remove deprecated backported mock module (#402) * Remove deprecated backported mock module --- .flake8 | 2 +- tests/base.py | 13 +- tests/mock.py | 993 ------------------------------------------- tests/test_api.py | 36 +- tests/test_client.py | 11 +- tests/test_unit.py | 5 +- 6 files changed, 33 insertions(+), 1027 deletions(-) delete mode 100644 tests/mock.py diff --git a/.flake8 b/.flake8 index 343f01039..4fc6605a0 100644 --- a/.flake8 +++ b/.flake8 @@ -10,4 +10,4 @@ [flake8] max-line-length = 120 -exclude = shotgun_api3/lib/httplib2/*,shotgun_api3/lib/six.py,tests/httplib2test.py,tests/mock.py +exclude = shotgun_api3/lib/httplib2/*,shotgun_api3/lib/six.py,tests/httplib2test.py diff --git a/tests/base.py b/tests/base.py index e30ec01a4..d1f138f47 100644 --- a/tests/base.py +++ b/tests/base.py @@ -8,10 +8,9 @@ import re import time import unittest +import unittest.mock import urllib.error -from . import mock - import shotgun_api3 as api from shotgun_api3.shotgun import ServerCapabilities from shotgun_api3.lib import six @@ -133,12 +132,12 @@ def _setup_mock(self, s3_status_code_error=503): """Setup mocking on the ShotgunClient to stop it calling a live server""" # Replace the function used to make the final call to the server # eaiser than mocking the http connection + response - self.sg._http_request = mock.Mock( + self.sg._http_request = unittest.mock.Mock( spec=api.Shotgun._http_request, return_value=((200, "OK"), {}, None) ) # Replace the function used to make the final call to the S3 server, and simulate # the exception HTTPError raised with 503 status errors - self.sg._make_upload_request = mock.Mock( + self.sg._make_upload_request = unittest.mock.Mock( spec=api.Shotgun._make_upload_request, side_effect=urllib.error.HTTPError( "url", @@ -152,12 +151,12 @@ def _setup_mock(self, s3_status_code_error=503): # also replace the function that is called to get the http connection # to avoid calling the server. OK to return a mock as we will not use # it - self.mock_conn = mock.Mock(spec=api.lib.httplib2.Http) + self.mock_conn = unittest.mock.Mock(spec=api.lib.httplib2.Http) # The Http objects connection property is a dict of connections # it is holding self.mock_conn.connections = dict() self.sg._connection = self.mock_conn - self.sg._get_connection = mock.Mock(return_value=self.mock_conn) + self.sg._get_connection = unittest.mock.Mock(return_value=self.mock_conn) # create the server caps directly to say we have the correct version self.sg._server_caps = ServerCapabilities( @@ -173,7 +172,7 @@ def _mock_http(self, data, headers=None, status=None): """ # test for a mock object rather than config.mock as some tests # force the mock to be created - if not isinstance(self.sg._http_request, mock.Mock): + if not isinstance(self.sg._http_request, unittest.mock.Mock): return if not isinstance(data, str): diff --git a/tests/mock.py b/tests/mock.py deleted file mode 100644 index 736571c64..000000000 --- a/tests/mock.py +++ /dev/null @@ -1,993 +0,0 @@ -# mock.py -# Test tools for mocking and patching. -# Copyright (C) 2007-2011 Michael Foord & the mock team -# E-mail: fuzzyman AT voidspace DOT org DOT uk - -# mock 0.7.0 -# http://www.voidspace.org.uk/python/mock/ - -# Released subject to the BSD License -# Please see http://www.voidspace.org.uk/python/license.shtml - -# Scripts maintained at http://www.voidspace.org.uk/python/index.shtml -# Comments, suggestions and bug reports welcome. - - -__all__ = ( - "Mock", - "MagicMock", - "mocksignature", - "patch", - "patch_object", - "sentinel", - "DEFAULT", -) - -__version__ = "0.7.0" - -__unittest = True - - -import sys -import warnings - -try: - import inspect -except ImportError: - # for alternative platforms that - # may not have inspect - inspect = None - -try: - from functools import wraps -except ImportError: - # Python 2.4 compatibility - def wraps(original): - def inner(f): - f.__name__ = original.__name__ - f.__doc__ = original.__doc__ - f.__module__ = original.__module__ - return f - - return inner - - -try: - unicode -except NameError: - # Python 3 - basestring = unicode = str - -try: - long -except NameError: - # Python 3 - long = int - -inPy3k = sys.version_info[0] == 3 - -if inPy3k: - self = "__self__" -else: - self = "im_self" - - -# getsignature and mocksignature heavily "inspired" by -# the decorator module: http://pypi.python.org/pypi/decorator/ -# by Michele Simionato - - -def _getsignature(func, skipfirst): - if inspect is None: - raise ImportError("inspect module not available") - - if inspect.isclass(func): - func = func.__init__ - # will have a self arg - skipfirst = True - elif not (inspect.ismethod(func) or inspect.isfunction(func)): - func = func.__call__ - - regargs, varargs, varkwargs, defaults = inspect.getargspec(func) - - # instance methods need to lose the self argument - if getattr(func, self, None) is not None: - regargs = regargs[1:] - - _msg = "_mock_ is a reserved argument name, can't mock signatures using _mock_" - assert "_mock_" not in regargs, _msg - if varargs is not None: - assert "_mock_" not in varargs, _msg - if varkwargs is not None: - assert "_mock_" not in varkwargs, _msg - if skipfirst: - regargs = regargs[1:] - signature = inspect.formatargspec( - regargs, varargs, varkwargs, defaults, formatvalue=lambda value: "" - ) - return signature[1:-1], func - - -def _copy_func_details(func, funcopy): - funcopy.__name__ = func.__name__ - funcopy.__doc__ = func.__doc__ - funcopy.__dict__.update(func.__dict__) - funcopy.__module__ = func.__module__ - if not inPy3k: - funcopy.func_defaults = func.func_defaults - else: - funcopy.__defaults__ = func.__defaults__ - funcopy.__kwdefaults__ = func.__kwdefaults__ - - -def mocksignature(func, mock=None, skipfirst=False): - """ - mocksignature(func, mock=None, skipfirst=False) - - Create a new function with the same signature as `func` that delegates - to `mock`. If `skipfirst` is True the first argument is skipped, useful - for methods where `self` needs to be omitted from the new function. - - If you don't pass in a `mock` then one will be created for you. - - The mock is set as the `mock` attribute of the returned function for easy - access. - - `mocksignature` can also be used with classes. It copies the signature of - the `__init__` method. - - When used with callable objects (instances) it copies the signature of the - `__call__` method. - """ - if mock is None: - mock = Mock() - signature, func = _getsignature(func, skipfirst) - src = "lambda %(signature)s: _mock_(%(signature)s)" % {"signature": signature} - - funcopy = eval(src, dict(_mock_=mock)) - _copy_func_details(func, funcopy) - funcopy.mock = mock - return funcopy - - -def _is_magic(name): - return "__%s__" % name[2:-2] == name - - -class SentinelObject(object): - "A unique, named, sentinel object." - - def __init__(self, name): - self.name = name - - def __repr__(self): - return '' % self.name - - -class Sentinel(object): - """Access attributes to return a named object, usable as a sentinel.""" - - def __init__(self): - self._sentinels = {} - - def __getattr__(self, name): - if name == "__bases__": - # Without this help(mock) raises an exception - raise AttributeError - return self._sentinels.setdefault(name, SentinelObject(name)) - - -sentinel = Sentinel() - -DEFAULT = sentinel.DEFAULT - - -class OldStyleClass: - pass - - -ClassType = type(OldStyleClass) - - -def _copy(value): - if type(value) in (dict, list, tuple, set): - return type(value)(value) - return value - - -if inPy3k: - class_types = type -else: - class_types = (type, ClassType) - - -class Mock(object): - """ - Create a new ``Mock`` object. ``Mock`` takes several optional arguments - that specify the behaviour of the Mock object: - - * ``spec``: This can be either a list of strings or an existing object (a - class or instance) that acts as the specification for the mock object. If - you pass in an object then a list of strings is formed by calling dir on - the object (excluding unsupported magic attributes and methods). Accessing - any attribute not in this list will raise an ``AttributeError``. - - If ``spec`` is an object (rather than a list of strings) then - `mock.__class__` returns the class of the spec object. This allows mocks - to pass `isinstance` tests. - - * ``spec_set``: A stricter variant of ``spec``. If used, attempting to *set* - or get an attribute on the mock that isn't on the object passed as - ``spec_set`` will raise an ``AttributeError``. - - * ``side_effect``: A function to be called whenever the Mock is called. See - the :attr:`Mock.side_effect` attribute. Useful for raising exceptions or - dynamically changing return values. The function is called with the same - arguments as the mock, and unless it returns :data:`DEFAULT`, the return - value of this function is used as the return value. - - Alternatively ``side_effect`` can be an exception class or instance. In - this case the exception will be raised when the mock is called. - - * ``return_value``: The value returned when the mock is called. By default - this is a new Mock (created on first access). See the - :attr:`Mock.return_value` attribute. - - * ``wraps``: Item for the mock object to wrap. If ``wraps`` is not None - then calling the Mock will pass the call through to the wrapped object - (returning the real result and ignoring ``return_value``). Attribute - access on the mock will return a Mock object that wraps the corresponding - attribute of the wrapped object (so attempting to access an attribute that - doesn't exist will raise an ``AttributeError``). - - If the mock has an explicit ``return_value`` set then calls are not passed - to the wrapped object and the ``return_value`` is returned instead. - - * ``name``: If the mock has a name then it will be used in the repr of the - mock. This can be useful for debugging. The name is propagated to child - mocks. - """ - - def __new__(cls, *args, **kw): - # every instance has its own class - # so we can create magic methods on the - # class without stomping on other mocks - new = type(cls.__name__, (cls,), {"__doc__": cls.__doc__}) - return object.__new__(new) - - def __init__( - self, - spec=None, - side_effect=None, - return_value=DEFAULT, - wraps=None, - name=None, - spec_set=None, - parent=None, - ): - self._parent = parent - self._name = name - _spec_class = None - if spec_set is not None: - spec = spec_set - spec_set = True - - if spec is not None and not isinstance(spec, list): - if isinstance(spec, class_types): - _spec_class = spec - else: - _spec_class = spec.__class__ - spec = dir(spec) - - self._spec_class = _spec_class - self._spec_set = spec_set - self._methods = spec - self._children = {} - self._return_value = return_value - self.side_effect = side_effect - self._wraps = wraps - - self.reset_mock() - - @property - def __class__(self): - if self._spec_class is None: - return type(self) - return self._spec_class - - def reset_mock(self): - "Restore the mock object to its initial state." - self.called = False - self.call_args = None - self.call_count = 0 - self.call_args_list = [] - self.method_calls = [] - for child in self._children.values(): - child.reset_mock() - if isinstance(self._return_value, Mock): - if not self._return_value is self: - self._return_value.reset_mock() - - def __get_return_value(self): - if self._return_value is DEFAULT: - self._return_value = self._get_child_mock() - return self._return_value - - def __set_return_value(self, value): - self._return_value = value - - __return_value_doc = "The value to be returned when the mock is called." - return_value = property(__get_return_value, __set_return_value, __return_value_doc) - - def __call__(self, *args, **kwargs): - self.called = True - self.call_count += 1 - self.call_args = callargs((args, kwargs)) - self.call_args_list.append(callargs((args, kwargs))) - - parent = self._parent - name = self._name - while parent is not None: - parent.method_calls.append(callargs((name, args, kwargs))) - if parent._parent is None: - break - name = parent._name + "." + name - parent = parent._parent - - ret_val = DEFAULT - if self.side_effect is not None: - if ( - isinstance(self.side_effect, BaseException) - or isinstance(self.side_effect, class_types) - and issubclass(self.side_effect, BaseException) - ): - raise self.side_effect - - ret_val = self.side_effect(*args, **kwargs) - if ret_val is DEFAULT: - ret_val = self.return_value - - if self._wraps is not None and self._return_value is DEFAULT: - return self._wraps(*args, **kwargs) - if ret_val is DEFAULT: - ret_val = self.return_value - return ret_val - - def __getattr__(self, name): - if name == "_methods": - raise AttributeError(name) - elif self._methods is not None: - if name not in self._methods or name in _all_magics: - raise AttributeError("Mock object has no attribute '%s'" % name) - elif _is_magic(name): - raise AttributeError(name) - - if name not in self._children: - wraps = None - if self._wraps is not None: - wraps = getattr(self._wraps, name) - self._children[name] = self._get_child_mock( - parent=self, name=name, wraps=wraps - ) - - return self._children[name] - - def __repr__(self): - if self._name is None and self._spec_class is None: - return object.__repr__(self) - - name_string = "" - spec_string = "" - if self._name is not None: - - def get_name(name): - if name is None: - return "mock" - return name - - parent = self._parent - name = self._name - while parent is not None: - name = get_name(parent._name) + "." + name - parent = parent._parent - name_string = " name=%r" % name - if self._spec_class is not None: - spec_string = " spec=%r" - if self._spec_set: - spec_string = " spec_set=%r" - spec_string = spec_string % self._spec_class.__name__ - return "<%s%s%s id='%s'>" % ( - type(self).__name__, - name_string, - spec_string, - id(self), - ) - - def __setattr__(self, name, value): - if not "method_calls" in self.__dict__: - # allow all attribute setting until initialisation is complete - return object.__setattr__(self, name, value) - if ( - self._spec_set - and self._methods is not None - and name not in self._methods - and name not in self.__dict__ - and name != "return_value" - ): - raise AttributeError("Mock object has no attribute '%s'" % name) - if name in _unsupported_magics: - msg = "Attempting to set unsupported magic method %r." % name - raise AttributeError(msg) - elif name in _all_magics: - if self._methods is not None and name not in self._methods: - raise AttributeError("Mock object has no attribute '%s'" % name) - - if not isinstance(value, Mock): - setattr(type(self), name, _get_method(name, value)) - original = value - real = lambda *args, **kw: original(self, *args, **kw) - value = mocksignature(value, real, skipfirst=True) - else: - setattr(type(self), name, value) - return object.__setattr__(self, name, value) - - def __delattr__(self, name): - if name in _all_magics and name in type(self).__dict__: - delattr(type(self), name) - return object.__delattr__(self, name) - - def assert_called_with(self, *args, **kwargs): - """ - assert that the mock was called with the specified arguments. - - Raises an AssertionError if the args and keyword args passed in are - different to the last call to the mock. - """ - if self.call_args is None: - raise AssertionError("Expected: %s\nNot called" % ((args, kwargs),)) - if not self.call_args == (args, kwargs): - raise AssertionError( - "Expected: %s\nCalled with: %s" % ((args, kwargs), self.call_args) - ) - - def assert_called_once_with(self, *args, **kwargs): - """ - assert that the mock was called exactly once and with the specified - arguments. - """ - if not self.call_count == 1: - msg = "Expected to be called once. Called %s times." % self.call_count - raise AssertionError(msg) - return self.assert_called_with(*args, **kwargs) - - def _get_child_mock(self, **kw): - klass = type(self).__mro__[1] - return klass(**kw) - - -class callargs(tuple): - """ - A tuple for holding the results of a call to a mock, either in the form - `(args, kwargs)` or `(name, args, kwargs)`. - - If args or kwargs are empty then a callargs tuple will compare equal to - a tuple without those values. This makes comparisons less verbose:: - - callargs('name', (), {}) == ('name',) - callargs('name', (1,), {}) == ('name', (1,)) - callargs((), {'a': 'b'}) == ({'a': 'b'},) - """ - - def __eq__(self, other): - if len(self) == 3: - if other[0] != self[0]: - return False - args_kwargs = self[1:] - other_args_kwargs = other[1:] - else: - args_kwargs = tuple(self) - other_args_kwargs = other - - if len(other_args_kwargs) == 0: - other_args, other_kwargs = (), {} - elif len(other_args_kwargs) == 1: - if isinstance(other_args_kwargs[0], tuple): - other_args = other_args_kwargs[0] - other_kwargs = {} - else: - other_args = () - other_kwargs = other_args_kwargs[0] - else: - other_args, other_kwargs = other_args_kwargs - - return tuple(args_kwargs) == (other_args, other_kwargs) - - -def _dot_lookup(thing, comp, import_path): - try: - return getattr(thing, comp) - except AttributeError: - __import__(import_path) - return getattr(thing, comp) - - -def _importer(target): - components = target.split(".") - import_path = components.pop(0) - thing = __import__(import_path) - - for comp in components: - import_path += ".%s" % comp - thing = _dot_lookup(thing, comp, import_path) - return thing - - -class _patch(object): - def __init__(self, target, attribute, new, spec, create, mocksignature, spec_set): - self.target = target - self.attribute = attribute - self.new = new - self.spec = spec - self.create = create - self.has_local = False - self.mocksignature = mocksignature - self.spec_set = spec_set - - def copy(self): - return _patch( - self.target, - self.attribute, - self.new, - self.spec, - self.create, - self.mocksignature, - self.spec_set, - ) - - def __call__(self, func): - if isinstance(func, class_types): - return self.decorate_class(func) - else: - return self.decorate_callable(func) - - def decorate_class(self, klass): - for attr in dir(klass): - attr_value = getattr(klass, attr) - if attr.startswith("test") and hasattr(attr_value, "__call__"): - setattr(klass, attr, self.copy()(attr_value)) - return klass - - def decorate_callable(self, func): - if hasattr(func, "patchings"): - func.patchings.append(self) - return func - - @wraps(func) - def patched(*args, **keywargs): - # don't use a with here (backwards compatability with 2.5) - extra_args = [] - for patching in patched.patchings: - arg = patching.__enter__() - if patching.new is DEFAULT: - extra_args.append(arg) - args += tuple(extra_args) - try: - return func(*args, **keywargs) - finally: - for patching in reversed(getattr(patched, "patchings", [])): - patching.__exit__() - - patched.patchings = [self] - if hasattr(func, "func_code"): - # not in Python 3 - patched.compat_co_firstlineno = getattr( - func, "compat_co_firstlineno", func.func_code.co_firstlineno - ) - return patched - - def get_original(self): - target = self.target - name = self.attribute - - original = DEFAULT - local = False - - try: - original = target.__dict__[name] - except (AttributeError, KeyError): - original = getattr(target, name, DEFAULT) - else: - local = True - - if not self.create and original is DEFAULT: - raise AttributeError("%s does not have the attribute %r" % (target, name)) - return original, local - - def __enter__(self): - """Perform the patch.""" - new, spec, spec_set = self.new, self.spec, self.spec_set - original, local = self.get_original() - if new is DEFAULT: - # XXXX what if original is DEFAULT - shouldn't use it as a spec - inherit = False - if spec_set == True: - spec_set = original - if isinstance(spec_set, class_types): - inherit = True - elif spec == True: - # set spec to the object we are replacing - spec = original - if isinstance(spec, class_types): - inherit = True - new = Mock(spec=spec, spec_set=spec_set) - if inherit: - new.return_value = Mock(spec=spec, spec_set=spec_set) - new_attr = new - if self.mocksignature: - new_attr = mocksignature(original, new) - - self.temp_original = original - self.is_local = local - setattr(self.target, self.attribute, new_attr) - return new - - def __exit__(self, *_): - """Undo the patch.""" - if self.is_local and self.temp_original is not DEFAULT: - setattr(self.target, self.attribute, self.temp_original) - else: - delattr(self.target, self.attribute) - if not self.create and not hasattr(self.target, self.attribute): - # needed for proxy objects like django settings - setattr(self.target, self.attribute, self.temp_original) - - del self.temp_original - del self.is_local - - start = __enter__ - stop = __exit__ - - -def _patch_object( - target, - attribute, - new=DEFAULT, - spec=None, - create=False, - mocksignature=False, - spec_set=None, -): - """ - patch.object(target, attribute, new=DEFAULT, spec=None, create=False, - mocksignature=False, spec_set=None) - - patch the named member (`attribute`) on an object (`target`) with a mock - object. - - Arguments new, spec, create, mocksignature and spec_set have the same - meaning as for patch. - """ - return _patch(target, attribute, new, spec, create, mocksignature, spec_set) - - -def patch_object(*args, **kwargs): - "A deprecated form of patch.object(...)" - warnings.warn(("Please use patch.object instead."), DeprecationWarning, 2) - return _patch_object(*args, **kwargs) - - -def patch( - target, new=DEFAULT, spec=None, create=False, mocksignature=False, spec_set=None -): - """ - ``patch`` acts as a function decorator, class decorator or a context - manager. Inside the body of the function or with statement, the ``target`` - (specified in the form `'PackageName.ModuleName.ClassName'`) is patched - with a ``new`` object. When the function/with statement exits the patch is - undone. - - The target is imported and the specified attribute patched with the new - object, so it must be importable from the environment you are calling the - decorator from. - - If ``new`` is omitted, then a new ``Mock`` is created and passed in as an - extra argument to the decorated function. - - The ``spec`` and ``spec_set`` keyword arguments are passed to the ``Mock`` - if patch is creating one for you. - - In addition you can pass ``spec=True`` or ``spec_set=True``, which causes - patch to pass in the object being mocked as the spec/spec_set object. - - If ``mocksignature`` is True then the patch will be done with a function - created by mocking the one being replaced. If the object being replaced is - a class then the signature of `__init__` will be copied. If the object - being replaced is a callable object then the signature of `__call__` will - be copied. - - By default ``patch`` will fail to replace attributes that don't exist. If - you pass in 'create=True' and the attribute doesn't exist, patch will - create the attribute for you when the patched function is called, and - delete it again afterwards. This is useful for writing tests against - attributes that your production code creates at runtime. It is off by by - default because it can be dangerous. With it switched on you can write - passing tests against APIs that don't actually exist! - - Patch can be used as a TestCase class decorator. It works by - decorating each test method in the class. This reduces the boilerplate - code when your test methods share a common patchings set. - - Patch can be used with the with statement, if this is available in your - version of Python. Here the patching applies to the indented block after - the with statement. If you use "as" then the patched object will be bound - to the name after the "as"; very useful if `patch` is creating a mock - object for you. - - `patch.dict(...)` and `patch.object(...)` are available for alternate - use-cases. - """ - try: - target, attribute = target.rsplit(".", 1) - except (TypeError, ValueError): - raise TypeError("Need a valid target to patch. You supplied: %r" % (target,)) - target = _importer(target) - return _patch(target, attribute, new, spec, create, mocksignature, spec_set) - - -class _patch_dict(object): - """ - Patch a dictionary and restore the dictionary to its original state after - the test. - - `in_dict` can be a dictionary or a mapping like container. If it is a - mapping then it must at least support getting, setting and deleting items - plus iterating over keys. - - `in_dict` can also be a string specifying the name of the dictionary, which - will then be fetched by importing it. - - `values` can be a dictionary of values to set in the dictionary. `values` - can also be an iterable of ``(key, value)`` pairs. - - If `clear` is True then the dictionary will be cleared before the new - values are set. - """ - - def __init__(self, in_dict, values=(), clear=False): - if isinstance(in_dict, basestring): - in_dict = _importer(in_dict) - self.in_dict = in_dict - # support any argument supported by dict(...) constructor - self.values = dict(values) - self.clear = clear - self._original = None - - def __call__(self, f): - if isinstance(f, class_types): - return self.decorate_class(f) - - @wraps(f) - def _inner(*args, **kw): - self._patch_dict() - try: - return f(*args, **kw) - finally: - self._unpatch_dict() - - return _inner - - def decorate_class(self, klass): - for attr in dir(klass): - attr_value = getattr(klass, attr) - if attr.startswith("test") and hasattr(attr_value, "__call__"): - decorator = _patch_dict(self.in_dict, self.values, self.clear) - decorated = decorator(attr_value) - setattr(klass, attr, decorated) - return klass - - def __enter__(self): - """Patch the dict.""" - self._patch_dict() - - def _patch_dict(self): - """Unpatch the dict.""" - values = self.values - in_dict = self.in_dict - clear = self.clear - - try: - original = in_dict.copy() - except AttributeError: - # dict like object with no copy method - # must support iteration over keys - original = {} - for key in in_dict: - original[key] = in_dict[key] - self._original = original - - if clear: - _clear_dict(in_dict) - - try: - in_dict.update(values) - except AttributeError: - # dict like object with no update method - for key in values: - in_dict[key] = values[key] - - def _unpatch_dict(self): - in_dict = self.in_dict - original = self._original - - _clear_dict(in_dict) - - try: - in_dict.update(original) - except AttributeError: - for key in original: - in_dict[key] = original[key] - - def __exit__(self, *args): - self._unpatch_dict() - return False - - start = __enter__ - stop = __exit__ - - -def _clear_dict(in_dict): - try: - in_dict.clear() - except AttributeError: - keys = list(in_dict) - for key in keys: - del in_dict[key] - - -patch.object = _patch_object -patch.dict = _patch_dict - - -magic_methods = ( - "lt le gt ge eq ne " - "getitem setitem delitem " - "len contains iter " - "hash str sizeof " - "enter exit " - "divmod neg pos abs invert " - "complex int float index " - "trunc floor ceil " -) - -numerics = "add sub mul div truediv floordiv mod lshift rshift and xor or pow " -inplace = " ".join("i%s" % n for n in numerics.split()) -right = " ".join("r%s" % n for n in numerics.split()) -extra = "" -if inPy3k: - extra = "bool next " -else: - extra = "unicode long nonzero oct hex " -# __truediv__ and __rtruediv__ not available in Python 3 either - -# not including __prepare__, __instancecheck__, __subclasscheck__ -# (as they are metaclass methods) -# __del__ is not supported at all as it causes problems if it exists - -_non_defaults = set( - "__%s__" % method - for method in [ - "cmp", - "getslice", - "setslice", - "coerce", - "subclasses", - "dir", - "format", - "get", - "set", - "delete", - "reversed", - "missing", - "reduce", - "reduce_ex", - "getinitargs", - "getnewargs", - "getstate", - "setstate", - "getformat", - "setformat", - "repr", - ] -) - - -def _get_method(name, func): - "Turns a callable object (like a mock) into a real function" - - def method(self, *args, **kw): - return func(self, *args, **kw) - - method.__name__ = name - return method - - -_magics = set( - "__%s__" % method - for method in " ".join([magic_methods, numerics, inplace, right, extra]).split() -) - -_all_magics = _magics | _non_defaults - -_unsupported_magics = set( - [ - "__getattr__", - "__setattr__", - "__init__", - "__new__", - "__prepare__" "__instancecheck__", - "__subclasscheck__", - "__del__", - ] -) - -_calculate_return_value = { - "__hash__": lambda self: object.__hash__(self), - "__str__": lambda self: object.__str__(self), - "__sizeof__": lambda self: object.__sizeof__(self), - "__unicode__": lambda self: unicode(object.__str__(self)), -} - -_return_values = { - "__int__": 1, - "__contains__": False, - "__len__": 0, - "__iter__": iter([]), - "__exit__": False, - "__complex__": 1j, - "__float__": 1.0, - "__bool__": True, - "__nonzero__": True, - "__oct__": "1", - "__hex__": "0x1", - "__long__": long(1), - "__index__": 1, -} - - -def _set_return_value(mock, method, name): - return_value = DEFAULT - if name in _return_values: - return_value = _return_values[name] - elif name in _calculate_return_value: - try: - return_value = _calculate_return_value[name](mock) - except AttributeError: - return_value = AttributeError(name) - if return_value is not DEFAULT: - method.return_value = return_value - - -class MagicMock(Mock): - """ - MagicMock is a subclass of :Mock with default implementations - of most of the magic methods. You can use MagicMock without having to - configure the magic methods yourself. - - If you use the ``spec`` or ``spec_set`` arguments then *only* magic - methods that exist in the spec will be created. - - Attributes and the return value of a `MagicMock` will also be `MagicMocks`. - """ - - def __init__(self, *args, **kw): - Mock.__init__(self, *args, **kw) - - these_magics = _magics - if self._methods is not None: - these_magics = _magics.intersection(self._methods) - - for entry in these_magics: - # could specify parent? - m = Mock() - setattr(self, entry, m) - _set_return_value(self, m, entry) diff --git a/tests/test_api.py b/tests/test_api.py index cd4dcbeef..d42328f3c 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -22,6 +22,7 @@ import time import types import unittest +import unittest.mock import urllib.parse import urllib.request import urllib.error @@ -39,8 +40,6 @@ import shotgun_api3 from . import base -from . import mock -from .mock import patch, MagicMock class TestShotgunApi(base.LiveTestBase): @@ -315,13 +314,12 @@ def test_upload_download(self): # cleanup os.remove(file_path) - @patch("shotgun_api3.Shotgun._send_form") + @unittest.mock.patch("shotgun_api3.Shotgun._send_form") def test_upload_to_sg(self, mock_send_form): """ Upload an attachment tests for _upload_to_sg() """ self.sg.server_info["s3_direct_uploads_enabled"] = False - mock_send_form.method.assert_called_once() mock_send_form.return_value = "1\n:123\nasd" this_dir, _ = os.path.split(__file__) u_path = os.path.abspath( @@ -334,6 +332,7 @@ def test_upload_to_sg(self, mock_send_form): "attachments", tag_list="monkeys, everywhere, send, help", ) + mock_send_form.assert_called_once() mock_send_form_args, _ = mock_send_form.call_args display_name_to_send = mock_send_form_args[1].get("display_name", "") self.assertTrue(isinstance(upload_id, int)) @@ -356,7 +355,7 @@ def test_upload_to_sg(self, mock_send_form): display_name_to_send.startswith("b'") and display_name_to_send.endswith("'") ) - mock_send_form.method.assert_called_once() + mock_send_form.reset_mock() mock_send_form.return_value = "2\nIt can't be upload" self.assertRaises( shotgun_api3.ShotgunError, @@ -367,6 +366,7 @@ def test_upload_to_sg(self, mock_send_form): "attachments", tag_list="monkeys, everywhere, send, help", ) + mock_send_form.assert_called_once() self.sg.server_info["s3_direct_uploads_enabled"] = True def test_upload_thumbnail_in_create(self): @@ -714,11 +714,10 @@ def share_thumbnail_retry(*args, **kwargs): shotgun_api3.ShotgunError, self.sg.share_thumbnail, [self.shot, self.asset] ) - @patch("shotgun_api3.Shotgun._send_form") + @unittest.mock.patch("shotgun_api3.Shotgun._send_form") def test_share_thumbnail_not_ready(self, mock_send_form): """throw an exception if trying to share a transient thumbnail""" - mock_send_form.method.assert_called_once() mock_send_form.return_value = ( "2" "\nsource_entity image is a transient thumbnail that cannot be shared. " @@ -732,11 +731,12 @@ def test_share_thumbnail_not_ready(self, mock_send_form): source_entity=self.asset, ) - @patch("shotgun_api3.Shotgun._send_form") + mock_send_form.assert_called_once() + + @unittest.mock.patch("shotgun_api3.Shotgun._send_form") def test_share_thumbnail_returns_error(self, mock_send_form): """throw an exception if server returns an error code""" - mock_send_form.method.assert_called_once() mock_send_form.return_value = "1\nerror message.\n" self.assertRaises( @@ -746,6 +746,8 @@ def test_share_thumbnail_returns_error(self, mock_send_form): source_entity=self.asset, ) + mock_send_form.assert_called_once() + def test_deprecated_functions(self): """Deprecated functions raise errors""" self.assertRaises(shotgun_api3.ShotgunError, self.sg.schema, "foo") @@ -2194,17 +2196,17 @@ def test_bad_auth(self): user = self.sg.find_one("HumanUser", [["login", "is", login]]) self.sg.update("HumanUser", user["id"], {"locked_until": None}) - @patch("shotgun_api3.shotgun.Http.request") + @unittest.mock.patch("shotgun_api3.shotgun.Http.request") def test_status_not_200(self, mock_request): - response = MagicMock(name="response mock", spec=dict) + response = unittest.mock.MagicMock(name="response mock", spec=dict) response.status = 300 response.reason = "reason" mock_request.return_value = (response, {}) self.assertRaises(shotgun_api3.ProtocolError, self.sg.find_one, "Shot", []) - @patch("shotgun_api3.shotgun.Http.request") + @unittest.mock.patch("shotgun_api3.shotgun.Http.request") def test_make_call_retry(self, mock_request): - response = MagicMock(name="response mock", spec=dict) + response = unittest.mock.MagicMock(name="response mock", spec=dict) response.status = 200 response.reason = "reason" mock_request.return_value = (response, {}) @@ -2234,7 +2236,7 @@ def my_side_effect2(*args, **kwargs): "EOF occurred in violation of protocol (_ssl.c:2426)" ) - return mock.DEFAULT + return unittest.mock.DEFAULT finally: my_side_effect2.counter += 1 @@ -2260,7 +2262,7 @@ def my_side_effect2(*args, **kwargs): finally: self.sg.config.rpc_attempt_interval = bak_rpc_attempt_interval - @patch("shotgun_api3.shotgun.Http.request") + @unittest.mock.patch("shotgun_api3.shotgun.Http.request") def test_sha2_error(self, mock_request): # Simulate the exception raised with SHA-2 errors mock_request.side_effect = ShotgunSSLError( @@ -2300,7 +2302,7 @@ def test_sha2_error(self, mock_request): if original_env_val is not None: os.environ["SHOTGUN_FORCE_CERTIFICATE_VALIDATION"] = original_env_val - @patch("shotgun_api3.shotgun.Http.request") + @unittest.mock.patch("shotgun_api3.shotgun.Http.request") def test_sha2_error_with_strict(self, mock_request): # Simulate the exception raised with SHA-2 errors mock_request.side_effect = ShotgunSSLError( @@ -2331,7 +2333,7 @@ def test_sha2_error_with_strict(self, mock_request): if original_env_val is not None: os.environ["SHOTGUN_FORCE_CERTIFICATE_VALIDATION"] = original_env_val - @patch.object(urllib.request.OpenerDirector, "open") + @unittest.mock.patch.object(urllib.request.OpenerDirector, "open") def test_sanitized_auth_params(self, mock_open): # Simulate the server blowing up and giving us a 500 error mock_open.side_effect = urllib.error.HTTPError("url", 500, "message", {}, None) diff --git a/tests/test_client.py b/tests/test_client.py index 164731908..f99a79806 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -22,13 +22,12 @@ import sys import time import unittest +import unittest.mock import urllib.parse import urllib.error from shotgun_api3.lib import six, sgutils -from . import mock - import shotgun_api3.lib.httplib2 as httplib2 import shotgun_api3 as api from shotgun_api3.shotgun import ServerCapabilities, SG_TIMEZONE @@ -321,7 +320,7 @@ def test_network_retry(self): """Network failure is retried, with a sleep call between retries.""" self.sg._http_request.side_effect = httplib2.HttpLib2Error - with mock.patch("time.sleep") as mock_sleep: + with unittest.mock.patch("time.sleep") as mock_sleep: self.assertRaises(httplib2.HttpLib2Error, self.sg.info) self.assertTrue( self.sg._http_request.call_count == 1, @@ -507,7 +506,7 @@ def test_upload_s3_urlerror__get_attachment_upload_info(self): """ Test URLError response is retried when invoking _send_form """ - mock_opener = mock.Mock() + mock_opener = unittest.mock.Mock() mock_opener.return_value.open.side_effect = urllib.error.URLError( "[WinError 10054] An existing connection was forcibly closed by the remote host" ) @@ -535,7 +534,7 @@ def test_upload_s3_urlerror__upload_to_storage(self): """ Test URLError response is retried when uploading to S3. """ - self.sg._make_upload_request = mock.Mock( + self.sg._make_upload_request = unittest.mock.Mock( spec=api.Shotgun._make_upload_request, side_effect=urllib.error.URLError( "[Errno 104] Connection reset by peer", @@ -684,7 +683,7 @@ def test_parse_records(self): }, } url = "http://foo/files/0000/0000/0012/232/shot_thumb.jpg" - self.sg._build_thumb_url = mock.Mock(return_value=url) + self.sg._build_thumb_url = unittest.mock.Mock(return_value=url) modified, txt = self.sg._parse_records([orig, "plain text"]) self.assertEqual("plain text", txt, "non dict value is left as is") diff --git a/tests/test_unit.py b/tests/test_unit.py index 58e46d366..445d1fe07 100644 --- a/tests/test_unit.py +++ b/tests/test_unit.py @@ -16,7 +16,6 @@ import urllib.request import urllib.error -from .mock import patch import shotgun_api3 as api from shotgun_api3.lib.httplib2 import Http, ssl_error_classes @@ -188,7 +187,7 @@ def test_filters(self): actual_condition = result["filters"]["conditions"][0] self.assertEqual(expected_condition, actual_condition) - @patch("shotgun_api3.Shotgun._call_rpc") + @mock.patch("shotgun_api3.Shotgun._call_rpc") def get_call_rpc_params(self, args, kws, call_rpc): """Return params sent to _call_rpc from summarize.""" if not args: @@ -301,7 +300,7 @@ def test_no_platform(self): finally: api.shotgun.sys.platform = platform - @patch("shotgun_api3.shotgun.sys") + @mock.patch("shotgun_api3.shotgun.sys") def test_py_version(self, mock_sys): major = 2 minor = 7 From c3888df486bf1a536ef0e9634df44d6f1746a868 Mon Sep 17 00:00:00 2001 From: Julien Langlois <16244608+julien-lang@users.noreply.github.com> Date: Wed, 10 Sep 2025 10:54:15 -0700 Subject: [PATCH 102/125] SG-38306 Python2 Removal - Part 6 - Remove python2 from httplib2 module (#403) * Remove python2 from httplib2 module --------- Co-authored-by: Eduardo Chauca --- shotgun_api3/lib/httplib2/__init__.py | 1838 +++++++++++++- shotgun_api3/lib/httplib2/auth.py | 2 +- shotgun_api3/lib/httplib2/python2/__init__.py | 1993 --------------- shotgun_api3/lib/httplib2/python2/auth.py | 63 - shotgun_api3/lib/httplib2/python2/cacerts.txt | 2225 ----------------- shotgun_api3/lib/httplib2/python2/certs.py | 42 - shotgun_api3/lib/httplib2/python2/error.py | 48 - shotgun_api3/lib/httplib2/python2/iri2uri.py | 123 - shotgun_api3/lib/httplib2/python2/socks.py | 518 ---- shotgun_api3/lib/httplib2/python3/__init__.py | 1799 ------------- shotgun_api3/lib/httplib2/python3/auth.py | 69 - shotgun_api3/lib/httplib2/python3/cacerts.txt | 2225 ----------------- shotgun_api3/lib/httplib2/python3/certs.py | 42 - shotgun_api3/lib/httplib2/python3/error.py | 48 - shotgun_api3/lib/httplib2/python3/iri2uri.py | 124 - shotgun_api3/lib/httplib2/python3/socks.py | 518 ---- shotgun_api3/shotgun.py | 4 +- tests/test_api.py | 15 - tests/test_unit.py | 5 +- update_httplib2.py | 19 +- 20 files changed, 1811 insertions(+), 9909 deletions(-) delete mode 100644 shotgun_api3/lib/httplib2/python2/__init__.py delete mode 100644 shotgun_api3/lib/httplib2/python2/auth.py delete mode 100644 shotgun_api3/lib/httplib2/python2/cacerts.txt delete mode 100644 shotgun_api3/lib/httplib2/python2/certs.py delete mode 100644 shotgun_api3/lib/httplib2/python2/error.py delete mode 100644 shotgun_api3/lib/httplib2/python2/iri2uri.py delete mode 100644 shotgun_api3/lib/httplib2/python2/socks.py delete mode 100644 shotgun_api3/lib/httplib2/python3/__init__.py delete mode 100644 shotgun_api3/lib/httplib2/python3/auth.py delete mode 100644 shotgun_api3/lib/httplib2/python3/cacerts.txt delete mode 100644 shotgun_api3/lib/httplib2/python3/certs.py delete mode 100644 shotgun_api3/lib/httplib2/python3/error.py delete mode 100644 shotgun_api3/lib/httplib2/python3/iri2uri.py delete mode 100644 shotgun_api3/lib/httplib2/python3/socks.py diff --git a/shotgun_api3/lib/httplib2/__init__.py b/shotgun_api3/lib/httplib2/__init__.py index 42c9916d1..ba5fa2f23 100644 --- a/shotgun_api3/lib/httplib2/__init__.py +++ b/shotgun_api3/lib/httplib2/__init__.py @@ -1,39 +1,1799 @@ -from .. import six - -# Define all here to keep linters happy. It should be overwritten by the code -# below, but if in the future __all__ is not defined in httplib2 this will keep -# things from breaking. -__all__ = [] - -# Import the proper implementation into the module namespace depending on the -# current python version. httplib2 supports python 2/3 by forking the code rather -# than with a single cross-compatible module. Rather than modify third party code, -# we'll just import the appropriate branch here. -if six.PY3: - # Generate ssl_error_classes - import ssl as __ssl - ssl_error_classes = (__ssl.SSLError, __ssl.CertificateError) - del __ssl - - # get the python3 fork of httplib2 - from . import python3 as __httplib2_compat - - -else: - # Generate ssl_error_classes - from .python2 import SSLHandshakeError as __SSLHandshakeError # TODO: shouldn't rely on this. not public - ssl_error_classes = (__SSLHandshakeError,) - del __SSLHandshakeError - - # get the python2 fork of httplib2 - from . import python2 as __httplib2_compat - -# Import all of the httplib2 module. Note that we can't use a star import because -# we need to import *everything*, not just what exists in __all__. -for __name in dir(__httplib2_compat): - globals()[__name] = getattr(__httplib2_compat, __name) -del __httplib2_compat -del __name - -# Add ssl_error_classes to __all__ -__all__.append("ssl_error_classes") +# -*- coding: utf-8 -*- +"""Small, fast HTTP client library for Python.""" + +__author__ = "Joe Gregorio (joe@bitworking.org)" +__copyright__ = "Copyright 2006, Joe Gregorio" +__contributors__ = [ + "Thomas Broyer (t.broyer@ltgt.net)", + "James Antill", + "Xavier Verges Farrero", + "Jonathan Feinberg", + "Blair Zajac", + "Sam Ruby", + "Louis Nyffenegger", + "Mark Pilgrim", + "Alex Yu", + "Lai Han", +] +__license__ = "MIT" +__version__ = "0.22.0" + +import base64 +import calendar +import copy +import email +import email.feedparser +from email import header +import email.message +import email.utils +import errno +from gettext import gettext as _ +import gzip +from hashlib import md5 as _md5 +from hashlib import sha1 as _sha +import hmac +import http.client +import io +import os +import random +import re +import socket +import ssl +import sys +import time +import urllib.parse +import zlib + +try: + import socks +except ImportError: + # TODO: remove this fallback and copypasted socksipy module upon py2/3 merge, + # idea is to have soft-dependency on any compatible module called socks + from . import socks +from . import auth +from .error import * +from .iri2uri import iri2uri + + +def has_timeout(timeout): + if hasattr(socket, "_GLOBAL_DEFAULT_TIMEOUT"): + return timeout is not None and timeout is not socket._GLOBAL_DEFAULT_TIMEOUT + return timeout is not None + + +__all__ = [ + "debuglevel", + "FailedToDecompressContent", + "Http", + "HttpLib2Error", + "ProxyInfo", + "RedirectLimit", + "RedirectMissingLocation", + "Response", + "RETRIES", + "UnimplementedDigestAuthOptionError", + "UnimplementedHmacDigestAuthOptionError", +] + +# The httplib debug level, set to a non-zero value to get debug output +debuglevel = 0 + +# A request will be tried 'RETRIES' times if it fails at the socket/connection level. +RETRIES = 2 + + +# Open Items: +# ----------- + +# Are we removing the cached content too soon on PUT (only delete on 200 Maybe?) + +# Pluggable cache storage (supports storing the cache in +# flat files by default. We need a plug-in architecture +# that can support Berkeley DB and Squid) + +# == Known Issues == +# Does not handle a resource that uses conneg and Last-Modified but no ETag as a cache validator. +# Does not handle Cache-Control: max-stale +# Does not use Age: headers when calculating cache freshness. + +# The number of redirections to follow before giving up. +# Note that only GET redirects are automatically followed. +# Will also honor 301 requests by saving that info and never +# requesting that URI again. +DEFAULT_MAX_REDIRECTS = 5 + +# Which headers are hop-by-hop headers by default +HOP_BY_HOP = [ + "connection", + "keep-alive", + "proxy-authenticate", + "proxy-authorization", + "te", + "trailers", + "transfer-encoding", + "upgrade", +] + +# https://tools.ietf.org/html/rfc7231#section-8.1.3 +SAFE_METHODS = ("GET", "HEAD", "OPTIONS", "TRACE") + +# To change, assign to `Http().redirect_codes` +REDIRECT_CODES = frozenset((300, 301, 302, 303, 307, 308)) + + +from . import certs + +CA_CERTS = certs.where() + +# PROTOCOL_TLS is python 3.5.3+. PROTOCOL_SSLv23 is deprecated. +# Both PROTOCOL_TLS and PROTOCOL_SSLv23 are equivalent and means: +# > Selects the highest protocol version that both the client and server support. +# > Despite the name, this option can select “TLS” protocols as well as “SSL”. +# source: https://docs.python.org/3.5/library/ssl.html#ssl.PROTOCOL_SSLv23 + +# PROTOCOL_TLS_CLIENT is python 3.10.0+. PROTOCOL_TLS is deprecated. +# > Auto-negotiate the highest protocol version that both the client and server support, and configure the context client-side connections. +# > The protocol enables CERT_REQUIRED and check_hostname by default. +# source: https://docs.python.org/3.10/library/ssl.html#ssl.PROTOCOL_TLS + +DEFAULT_TLS_VERSION = getattr(ssl, "PROTOCOL_TLS_CLIENT", None) or getattr(ssl, "PROTOCOL_TLS", None) or getattr(ssl, "PROTOCOL_SSLv23") + + +def _build_ssl_context( + disable_ssl_certificate_validation, + ca_certs, + cert_file=None, + key_file=None, + maximum_version=None, + minimum_version=None, + key_password=None, +): + if not hasattr(ssl, "SSLContext"): + raise RuntimeError("httplib2 requires Python 3.2+ for ssl.SSLContext") + + context = ssl.SSLContext(DEFAULT_TLS_VERSION) + # check_hostname and verify_mode should be set in opposite order during disable + # https://bugs.python.org/issue31431 + if disable_ssl_certificate_validation and hasattr(context, "check_hostname"): + context.check_hostname = not disable_ssl_certificate_validation + context.verify_mode = ssl.CERT_NONE if disable_ssl_certificate_validation else ssl.CERT_REQUIRED + + # SSLContext.maximum_version and SSLContext.minimum_version are python 3.7+. + # source: https://docs.python.org/3/library/ssl.html#ssl.SSLContext.maximum_version + if maximum_version is not None: + if hasattr(context, "maximum_version"): + if isinstance(maximum_version, str): + maximum_version = getattr(ssl.TLSVersion, maximum_version) + context.maximum_version = maximum_version + else: + raise RuntimeError("setting tls_maximum_version requires Python 3.7 and OpenSSL 1.1 or newer") + if minimum_version is not None: + if hasattr(context, "minimum_version"): + if isinstance(minimum_version, str): + minimum_version = getattr(ssl.TLSVersion, minimum_version) + context.minimum_version = minimum_version + else: + raise RuntimeError("setting tls_minimum_version requires Python 3.7 and OpenSSL 1.1 or newer") + # check_hostname requires python 3.4+ + # we will perform the equivalent in HTTPSConnectionWithTimeout.connect() by calling ssl.match_hostname + # if check_hostname is not supported. + if hasattr(context, "check_hostname"): + context.check_hostname = not disable_ssl_certificate_validation + + context.load_verify_locations(ca_certs) + + if cert_file: + context.load_cert_chain(cert_file, key_file, key_password) + + return context + + +def _get_end2end_headers(response): + hopbyhop = list(HOP_BY_HOP) + hopbyhop.extend([x.strip() for x in response.get("connection", "").split(",")]) + return [header for header in list(response.keys()) if header not in hopbyhop] + + +_missing = object() + + +def _errno_from_exception(e): + # TODO python 3.11+ cheap try: return e.errno except AttributeError: pass + errno = getattr(e, "errno", _missing) + if errno is not _missing: + return errno + + # socket.error and common wrap in .args + args = getattr(e, "args", None) + if args: + return _errno_from_exception(args[0]) + + # pysocks.ProxyError wraps in .socket_err + # https://github.com/httplib2/httplib2/pull/202 + socket_err = getattr(e, "socket_err", None) + if socket_err: + return _errno_from_exception(socket_err) + + return None + + +URI = re.compile(r"^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?") + + +def parse_uri(uri): + """Parses a URI using the regex given in Appendix B of RFC 3986. + + (scheme, authority, path, query, fragment) = parse_uri(uri) + """ + groups = URI.match(uri).groups() + return (groups[1], groups[3], groups[4], groups[6], groups[8]) + + +def urlnorm(uri): + (scheme, authority, path, query, fragment) = parse_uri(uri) + if not scheme or not authority: + raise RelativeURIError("Only absolute URIs are allowed. uri = %s" % uri) + authority = authority.lower() + scheme = scheme.lower() + if not path: + path = "/" + # Could do syntax based normalization of the URI before + # computing the digest. See Section 6.2.2 of Std 66. + request_uri = query and "?".join([path, query]) or path + scheme = scheme.lower() + defrag_uri = scheme + "://" + authority + request_uri + return scheme, authority, request_uri, defrag_uri + + +# Cache filename construction (original borrowed from Venus http://intertwingly.net/code/venus/) +re_url_scheme = re.compile(r"^\w+://") +re_unsafe = re.compile(r"[^\w\-_.()=!]+", re.ASCII) + + +def safename(filename): + """Return a filename suitable for the cache. + Strips dangerous and common characters to create a filename we + can use to store the cache in. + """ + if isinstance(filename, bytes): + filename_bytes = filename + filename = filename.decode("utf-8") + else: + filename_bytes = filename.encode("utf-8") + filemd5 = _md5(filename_bytes).hexdigest() + filename = re_url_scheme.sub("", filename) + filename = re_unsafe.sub("", filename) + + # limit length of filename (vital for Windows) + # https://github.com/httplib2/httplib2/pull/74 + # C:\Users\ \AppData\Local\Temp\ , + # 9 chars + max 104 chars + 20 chars + x + 1 + 32 = max 259 chars + # Thus max safe filename x = 93 chars. Let it be 90 to make a round sum: + filename = filename[:90] + + return ",".join((filename, filemd5)) + + +NORMALIZE_SPACE = re.compile(r"(?:\r\n)?[ \t]+") + + +def _normalize_headers(headers): + return dict( + [ + (_convert_byte_str(key).lower(), NORMALIZE_SPACE.sub(_convert_byte_str(value), " ").strip(),) + for (key, value) in headers.items() + ] + ) + + +def _convert_byte_str(s): + if not isinstance(s, str): + return str(s, "utf-8") + return s + + +def _parse_cache_control(headers): + retval = {} + if "cache-control" in headers: + parts = headers["cache-control"].split(",") + parts_with_args = [ + tuple([x.strip().lower() for x in part.split("=", 1)]) for part in parts if -1 != part.find("=") + ] + parts_wo_args = [(name.strip().lower(), 1) for name in parts if -1 == name.find("=")] + retval = dict(parts_with_args + parts_wo_args) + return retval + + +# Whether to use a strict mode to parse WWW-Authenticate headers +# Might lead to bad results in case of ill-formed header value, +# so disabled by default, falling back to relaxed parsing. +# Set to true to turn on, useful for testing servers. +USE_WWW_AUTH_STRICT_PARSING = 0 + + +def _entry_disposition(response_headers, request_headers): + """Determine freshness from the Date, Expires and Cache-Control headers. + + We don't handle the following: + + 1. Cache-Control: max-stale + 2. Age: headers are not used in the calculations. + + Not that this algorithm is simpler than you might think + because we are operating as a private (non-shared) cache. + This lets us ignore 's-maxage'. We can also ignore + 'proxy-invalidate' since we aren't a proxy. + We will never return a stale document as + fresh as a design decision, and thus the non-implementation + of 'max-stale'. This also lets us safely ignore 'must-revalidate' + since we operate as if every server has sent 'must-revalidate'. + Since we are private we get to ignore both 'public' and + 'private' parameters. We also ignore 'no-transform' since + we don't do any transformations. + The 'no-store' parameter is handled at a higher level. + So the only Cache-Control parameters we look at are: + + no-cache + only-if-cached + max-age + min-fresh + """ + + retval = "STALE" + cc = _parse_cache_control(request_headers) + cc_response = _parse_cache_control(response_headers) + + if "pragma" in request_headers and request_headers["pragma"].lower().find("no-cache") != -1: + retval = "TRANSPARENT" + if "cache-control" not in request_headers: + request_headers["cache-control"] = "no-cache" + elif "no-cache" in cc: + retval = "TRANSPARENT" + elif "no-cache" in cc_response: + retval = "STALE" + elif "only-if-cached" in cc: + retval = "FRESH" + elif "date" in response_headers: + date = calendar.timegm(email.utils.parsedate_tz(response_headers["date"])) + now = time.time() + current_age = max(0, now - date) + if "max-age" in cc_response: + try: + freshness_lifetime = int(cc_response["max-age"]) + except ValueError: + freshness_lifetime = 0 + elif "expires" in response_headers: + expires = email.utils.parsedate_tz(response_headers["expires"]) + if None == expires: + freshness_lifetime = 0 + else: + freshness_lifetime = max(0, calendar.timegm(expires) - date) + else: + freshness_lifetime = 0 + if "max-age" in cc: + try: + freshness_lifetime = int(cc["max-age"]) + except ValueError: + freshness_lifetime = 0 + if "min-fresh" in cc: + try: + min_fresh = int(cc["min-fresh"]) + except ValueError: + min_fresh = 0 + current_age += min_fresh + if freshness_lifetime > current_age: + retval = "FRESH" + return retval + + +def _decompressContent(response, new_content): + content = new_content + try: + encoding = response.get("content-encoding", None) + if encoding in ["gzip", "deflate"]: + if encoding == "gzip": + content = gzip.GzipFile(fileobj=io.BytesIO(new_content)).read() + if encoding == "deflate": + try: + content = zlib.decompress(content, zlib.MAX_WBITS) + except (IOError, zlib.error): + content = zlib.decompress(content, -zlib.MAX_WBITS) + response["content-length"] = str(len(content)) + # Record the historical presence of the encoding in a way the won't interfere. + response["-content-encoding"] = response["content-encoding"] + del response["content-encoding"] + except (IOError, zlib.error): + content = "" + raise FailedToDecompressContent( + _("Content purported to be compressed with %s but failed to decompress.") % response.get("content-encoding"), + response, + content, + ) + return content + + +def _bind_write_headers(msg): + def _write_headers(self): + # Self refers to the Generator object. + for h, v in msg.items(): + print("%s:" % h, end=" ", file=self._fp) + if isinstance(v, header.Header): + print(v.encode(maxlinelen=self._maxheaderlen), file=self._fp) + else: + # email.Header got lots of smarts, so use it. + headers = header.Header(v, maxlinelen=self._maxheaderlen, charset="utf-8", header_name=h) + print(headers.encode(), file=self._fp) + # A blank line always separates headers from body. + print(file=self._fp) + + return _write_headers + + +def _updateCache(request_headers, response_headers, content, cache, cachekey): + if cachekey: + cc = _parse_cache_control(request_headers) + cc_response = _parse_cache_control(response_headers) + if "no-store" in cc or "no-store" in cc_response: + cache.delete(cachekey) + else: + info = email.message.Message() + for key, value in response_headers.items(): + if key not in ["status", "content-encoding", "transfer-encoding"]: + info[key] = value + + # Add annotations to the cache to indicate what headers + # are variant for this request. + vary = response_headers.get("vary", None) + if vary: + vary_headers = vary.lower().replace(" ", "").split(",") + for header in vary_headers: + key = "-varied-%s" % header + try: + info[key] = request_headers[header] + except KeyError: + pass + + status = response_headers.status + if status == 304: + status = 200 + + status_header = "status: %d\r\n" % status + + try: + header_str = info.as_string() + except UnicodeEncodeError: + setattr(info, "_write_headers", _bind_write_headers(info)) + header_str = info.as_string() + + header_str = re.sub("\r(?!\n)|(? 0: + service = "cl" + # No point in guessing Base or Spreadsheet + # elif request_uri.find("spreadsheets") > 0: + # service = "wise" + + auth = dict(Email=credentials[0], Passwd=credentials[1], service=service, source=headers["user-agent"],) + resp, content = self.http.request( + "https://www.google.com/accounts/ClientLogin", + method="POST", + body=urlencode(auth), + headers={"Content-Type": "application/x-www-form-urlencoded"}, + ) + lines = content.split("\n") + d = dict([tuple(line.split("=", 1)) for line in lines if line]) + if resp.status == 403: + self.Auth = "" + else: + self.Auth = d["Auth"] + + def request(self, method, request_uri, headers, content): + """Modify the request headers to add the appropriate + Authorization header.""" + headers["authorization"] = "GoogleLogin Auth=" + self.Auth + + +AUTH_SCHEME_CLASSES = { + "basic": BasicAuthentication, + "wsse": WsseAuthentication, + "digest": DigestAuthentication, + "hmacdigest": HmacDigestAuthentication, + "googlelogin": GoogleLoginAuthentication, +} + +AUTH_SCHEME_ORDER = ["hmacdigest", "googlelogin", "digest", "wsse", "basic"] + + +class FileCache(object): + """Uses a local directory as a store for cached files. + Not really safe to use if multiple threads or processes are going to + be running on the same cache. + """ + + def __init__(self, cache, safe=safename): # use safe=lambda x: md5.new(x).hexdigest() for the old behavior + self.cache = cache + self.safe = safe + if not os.path.exists(cache): + os.makedirs(self.cache) + + def get(self, key): + retval = None + cacheFullPath = os.path.join(self.cache, self.safe(key)) + try: + f = open(cacheFullPath, "rb") + retval = f.read() + f.close() + except IOError: + pass + return retval + + def set(self, key, value): + cacheFullPath = os.path.join(self.cache, self.safe(key)) + f = open(cacheFullPath, "wb") + f.write(value) + f.close() + + def delete(self, key): + cacheFullPath = os.path.join(self.cache, self.safe(key)) + if os.path.exists(cacheFullPath): + os.remove(cacheFullPath) + + +class Credentials(object): + def __init__(self): + self.credentials = [] + + def add(self, name, password, domain=""): + self.credentials.append((domain.lower(), name, password)) + + def clear(self): + self.credentials = [] + + def iter(self, domain): + for (cdomain, name, password) in self.credentials: + if cdomain == "" or domain == cdomain: + yield (name, password) + + +class KeyCerts(Credentials): + """Identical to Credentials except that + name/password are mapped to key/cert.""" + + def add(self, key, cert, domain, password): + self.credentials.append((domain.lower(), key, cert, password)) + + def iter(self, domain): + for (cdomain, key, cert, password) in self.credentials: + if cdomain == "" or domain == cdomain: + yield (key, cert, password) + + +class AllHosts(object): + pass + + +class ProxyInfo(object): + """Collect information required to use a proxy.""" + + bypass_hosts = () + + def __init__( + self, proxy_type, proxy_host, proxy_port, proxy_rdns=True, proxy_user=None, proxy_pass=None, proxy_headers=None, + ): + """Args: + + proxy_type: The type of proxy server. This must be set to one of + socks.PROXY_TYPE_XXX constants. For example: p = + ProxyInfo(proxy_type=socks.PROXY_TYPE_HTTP, proxy_host='localhost', + proxy_port=8000) + proxy_host: The hostname or IP address of the proxy server. + proxy_port: The port that the proxy server is running on. + proxy_rdns: If True (default), DNS queries will not be performed + locally, and instead, handed to the proxy to resolve. This is useful + if the network does not allow resolution of non-local names. In + httplib2 0.9 and earlier, this defaulted to False. + proxy_user: The username used to authenticate with the proxy server. + proxy_pass: The password used to authenticate with the proxy server. + proxy_headers: Additional or modified headers for the proxy connect + request. + """ + if isinstance(proxy_user, bytes): + proxy_user = proxy_user.decode() + if isinstance(proxy_pass, bytes): + proxy_pass = proxy_pass.decode() + ( + self.proxy_type, + self.proxy_host, + self.proxy_port, + self.proxy_rdns, + self.proxy_user, + self.proxy_pass, + self.proxy_headers, + ) = ( + proxy_type, + proxy_host, + proxy_port, + proxy_rdns, + proxy_user, + proxy_pass, + proxy_headers, + ) + + def astuple(self): + return ( + self.proxy_type, + self.proxy_host, + self.proxy_port, + self.proxy_rdns, + self.proxy_user, + self.proxy_pass, + self.proxy_headers, + ) + + def isgood(self): + return socks and (self.proxy_host != None) and (self.proxy_port != None) + + def applies_to(self, hostname): + return not self.bypass_host(hostname) + + def bypass_host(self, hostname): + """Has this host been excluded from the proxy config""" + if self.bypass_hosts is AllHosts: + return True + + hostname = "." + hostname.lstrip(".") + for skip_name in self.bypass_hosts: + # *.suffix + if skip_name.startswith(".") and hostname.endswith(skip_name): + return True + # exact match + if hostname == "." + skip_name: + return True + return False + + def __repr__(self): + return ( + "" + ).format(p=self) + + +def proxy_info_from_environment(method="http"): + """Read proxy info from the environment variables. + """ + if method not in ("http", "https"): + return + + env_var = method + "_proxy" + url = os.environ.get(env_var, os.environ.get(env_var.upper())) + if not url: + return + return proxy_info_from_url(url, method, noproxy=None) + + +def proxy_info_from_url(url, method="http", noproxy=None): + """Construct a ProxyInfo from a URL (such as http_proxy env var) + """ + url = urllib.parse.urlparse(url) + + proxy_type = 3 # socks.PROXY_TYPE_HTTP + pi = ProxyInfo( + proxy_type=proxy_type, + proxy_host=url.hostname, + proxy_port=url.port or dict(https=443, http=80)[method], + proxy_user=url.username or None, + proxy_pass=url.password or None, + proxy_headers=None, + ) + + bypass_hosts = [] + # If not given an explicit noproxy value, respect values in env vars. + if noproxy is None: + noproxy = os.environ.get("no_proxy", os.environ.get("NO_PROXY", "")) + # Special case: A single '*' character means all hosts should be bypassed. + if noproxy == "*": + bypass_hosts = AllHosts + elif noproxy.strip(): + bypass_hosts = noproxy.split(",") + bypass_hosts = tuple(filter(bool, bypass_hosts)) # To exclude empty string. + + pi.bypass_hosts = bypass_hosts + return pi + + +class HTTPConnectionWithTimeout(http.client.HTTPConnection): + """HTTPConnection subclass that supports timeouts + + HTTPConnection subclass that supports timeouts + + All timeouts are in seconds. If None is passed for timeout then + Python's default timeout for sockets will be used. See for example + the docs of socket.setdefaulttimeout(): + http://docs.python.org/library/socket.html#socket.setdefaulttimeout + """ + + def __init__(self, host, port=None, timeout=None, proxy_info=None): + http.client.HTTPConnection.__init__(self, host, port=port, timeout=timeout) + + self.proxy_info = proxy_info + if proxy_info and not isinstance(proxy_info, ProxyInfo): + self.proxy_info = proxy_info("http") + + def connect(self): + """Connect to the host and port specified in __init__.""" + if self.proxy_info and socks is None: + raise ProxiesUnavailableError("Proxy support missing but proxy use was requested!") + if self.proxy_info and self.proxy_info.isgood() and self.proxy_info.applies_to(self.host): + use_proxy = True + ( + proxy_type, + proxy_host, + proxy_port, + proxy_rdns, + proxy_user, + proxy_pass, + proxy_headers, + ) = self.proxy_info.astuple() + + host = proxy_host + port = proxy_port + else: + use_proxy = False + + host = self.host + port = self.port + proxy_type = None + + socket_err = None + + for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM): + af, socktype, proto, canonname, sa = res + try: + if use_proxy: + self.sock = socks.socksocket(af, socktype, proto) + self.sock.setproxy( + proxy_type, proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, + ) + else: + self.sock = socket.socket(af, socktype, proto) + self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + if has_timeout(self.timeout): + self.sock.settimeout(self.timeout) + if self.debuglevel > 0: + print("connect: ({0}, {1}) ************".format(self.host, self.port)) + if use_proxy: + print( + "proxy: {0} ************".format( + str((proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers,)) + ) + ) + + self.sock.connect((self.host, self.port) + sa[2:]) + except socket.error as e: + socket_err = e + if self.debuglevel > 0: + print("connect fail: ({0}, {1})".format(self.host, self.port)) + if use_proxy: + print( + "proxy: {0}".format( + str((proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers,)) + ) + ) + if self.sock: + self.sock.close() + self.sock = None + continue + break + if not self.sock: + raise socket_err + + +class HTTPSConnectionWithTimeout(http.client.HTTPSConnection): + """This class allows communication via SSL. + + All timeouts are in seconds. If None is passed for timeout then + Python's default timeout for sockets will be used. See for example + the docs of socket.setdefaulttimeout(): + http://docs.python.org/library/socket.html#socket.setdefaulttimeout + """ + + def __init__( + self, + host, + port=None, + key_file=None, + cert_file=None, + timeout=None, + proxy_info=None, + ca_certs=None, + disable_ssl_certificate_validation=False, + tls_maximum_version=None, + tls_minimum_version=None, + key_password=None, + ): + + self.disable_ssl_certificate_validation = disable_ssl_certificate_validation + self.ca_certs = ca_certs if ca_certs else CA_CERTS + + self.proxy_info = proxy_info + if proxy_info and not isinstance(proxy_info, ProxyInfo): + self.proxy_info = proxy_info("https") + + context = _build_ssl_context( + self.disable_ssl_certificate_validation, + self.ca_certs, + cert_file, + key_file, + maximum_version=tls_maximum_version, + minimum_version=tls_minimum_version, + key_password=key_password, + ) + super(HTTPSConnectionWithTimeout, self).__init__( + host, port=port, timeout=timeout, context=context, + ) + self.key_file = key_file + self.cert_file = cert_file + self.key_password = key_password + + def connect(self): + """Connect to a host on a given (SSL) port.""" + if self.proxy_info and self.proxy_info.isgood() and self.proxy_info.applies_to(self.host): + use_proxy = True + ( + proxy_type, + proxy_host, + proxy_port, + proxy_rdns, + proxy_user, + proxy_pass, + proxy_headers, + ) = self.proxy_info.astuple() + + host = proxy_host + port = proxy_port + else: + use_proxy = False + + host = self.host + port = self.port + proxy_type = None + proxy_headers = None + + socket_err = None + + address_info = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM) + for family, socktype, proto, canonname, sockaddr in address_info: + try: + if use_proxy: + sock = socks.socksocket(family, socktype, proto) + + sock.setproxy( + proxy_type, proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, + ) + else: + sock = socket.socket(family, socktype, proto) + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + if has_timeout(self.timeout): + sock.settimeout(self.timeout) + sock.connect((self.host, self.port)) + + self.sock = self._context.wrap_socket(sock, server_hostname=self.host) + + # Python 3.3 compatibility: emulate the check_hostname behavior + if not hasattr(self._context, "check_hostname") and not self.disable_ssl_certificate_validation: + try: + ssl.match_hostname(self.sock.getpeercert(), self.host) + except Exception: + self.sock.shutdown(socket.SHUT_RDWR) + self.sock.close() + raise + + if self.debuglevel > 0: + print("connect: ({0}, {1})".format(self.host, self.port)) + if use_proxy: + print( + "proxy: {0}".format( + str((proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers,)) + ) + ) + except (ssl.SSLError, ssl.CertificateError) as e: + if sock: + sock.close() + if self.sock: + self.sock.close() + self.sock = None + raise + except (socket.timeout, socket.gaierror): + raise + except socket.error as e: + socket_err = e + if self.debuglevel > 0: + print("connect fail: ({0}, {1})".format(self.host, self.port)) + if use_proxy: + print( + "proxy: {0}".format( + str((proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers,)) + ) + ) + if self.sock: + self.sock.close() + self.sock = None + continue + break + if not self.sock: + raise socket_err + + +SCHEME_TO_CONNECTION = { + "http": HTTPConnectionWithTimeout, + "https": HTTPSConnectionWithTimeout, +} + + +class Http(object): + """An HTTP client that handles: + + - all methods + - caching + - ETags + - compression, + - HTTPS + - Basic + - Digest + - WSSE + + and more. + """ + + def __init__( + self, + cache=None, + timeout=None, + proxy_info=proxy_info_from_environment, + ca_certs=None, + disable_ssl_certificate_validation=False, + tls_maximum_version=None, + tls_minimum_version=None, + ): + """If 'cache' is a string then it is used as a directory name for + a disk cache. Otherwise it must be an object that supports the + same interface as FileCache. + + All timeouts are in seconds. If None is passed for timeout + then Python's default timeout for sockets will be used. See + for example the docs of socket.setdefaulttimeout(): + http://docs.python.org/library/socket.html#socket.setdefaulttimeout + + `proxy_info` may be: + - a callable that takes the http scheme ('http' or 'https') and + returns a ProxyInfo instance per request. By default, uses + proxy_info_from_environment. + - a ProxyInfo instance (static proxy config). + - None (proxy disabled). + + ca_certs is the path of a file containing root CA certificates for SSL + server certificate validation. By default, a CA cert file bundled with + httplib2 is used. + + If disable_ssl_certificate_validation is true, SSL cert validation will + not be performed. + + tls_maximum_version / tls_minimum_version require Python 3.7+ / + OpenSSL 1.1.0g+. A value of "TLSv1_3" requires OpenSSL 1.1.1+. + """ + self.proxy_info = proxy_info + self.ca_certs = ca_certs + self.disable_ssl_certificate_validation = disable_ssl_certificate_validation + self.tls_maximum_version = tls_maximum_version + self.tls_minimum_version = tls_minimum_version + # Map domain name to an httplib connection + self.connections = {} + # The location of the cache, for now a directory + # where cached responses are held. + if cache and isinstance(cache, str): + self.cache = FileCache(cache) + else: + self.cache = cache + + # Name/password + self.credentials = Credentials() + + # Key/cert + self.certificates = KeyCerts() + + # authorization objects + self.authorizations = [] + + # If set to False then no redirects are followed, even safe ones. + self.follow_redirects = True + + self.redirect_codes = REDIRECT_CODES + + # Which HTTP methods do we apply optimistic concurrency to, i.e. + # which methods get an "if-match:" etag header added to them. + self.optimistic_concurrency_methods = ["PUT", "PATCH"] + + self.safe_methods = list(SAFE_METHODS) + + # If 'follow_redirects' is True, and this is set to True then + # all redirecs are followed, including unsafe ones. + self.follow_all_redirects = False + + self.ignore_etag = False + + self.force_exception_to_status_code = False + + self.timeout = timeout + + # Keep Authorization: headers on a redirect. + self.forward_authorization_headers = False + + def close(self): + """Close persistent connections, clear sensitive data. + Not thread-safe, requires external synchronization against concurrent requests. + """ + existing, self.connections = self.connections, {} + for _, c in existing.items(): + c.close() + self.certificates.clear() + self.clear_credentials() + + def __getstate__(self): + state_dict = copy.copy(self.__dict__) + # In case request is augmented by some foreign object such as + # credentials which handle auth + if "request" in state_dict: + del state_dict["request"] + if "connections" in state_dict: + del state_dict["connections"] + return state_dict + + def __setstate__(self, state): + self.__dict__.update(state) + self.connections = {} + + def _auth_from_challenge(self, host, request_uri, headers, response, content): + """A generator that creates Authorization objects + that can be applied to requests. + """ + challenges = auth._parse_www_authenticate(response, "www-authenticate") + for cred in self.credentials.iter(host): + for scheme in AUTH_SCHEME_ORDER: + if scheme in challenges: + yield AUTH_SCHEME_CLASSES[scheme](cred, host, request_uri, headers, response, content, self) + + def add_credentials(self, name, password, domain=""): + """Add a name and password that will be used + any time a request requires authentication.""" + self.credentials.add(name, password, domain) + + def add_certificate(self, key, cert, domain, password=None): + """Add a key and cert that will be used + any time a request requires authentication.""" + self.certificates.add(key, cert, domain, password) + + def clear_credentials(self): + """Remove all the names and passwords + that are used for authentication""" + self.credentials.clear() + self.authorizations = [] + + def _conn_request(self, conn, request_uri, method, body, headers): + i = 0 + seen_bad_status_line = False + while i < RETRIES: + i += 1 + try: + if conn.sock is None: + conn.connect() + conn.request(method, request_uri, body, headers) + except socket.timeout: + conn.close() + raise + except socket.gaierror: + conn.close() + raise ServerNotFoundError("Unable to find the server at %s" % conn.host) + except socket.error as e: + errno_ = _errno_from_exception(e) + if errno_ in (errno.ENETUNREACH, errno.EADDRNOTAVAIL) and i < RETRIES: + continue # retry on potentially transient errors + raise + except http.client.HTTPException: + if conn.sock is None: + if i < RETRIES - 1: + conn.close() + conn.connect() + continue + else: + conn.close() + raise + if i < RETRIES - 1: + conn.close() + conn.connect() + continue + # Just because the server closed the connection doesn't apparently mean + # that the server didn't send a response. + pass + try: + response = conn.getresponse() + except (http.client.BadStatusLine, http.client.ResponseNotReady): + # If we get a BadStatusLine on the first try then that means + # the connection just went stale, so retry regardless of the + # number of RETRIES set. + if not seen_bad_status_line and i == 1: + i = 0 + seen_bad_status_line = True + conn.close() + conn.connect() + continue + else: + conn.close() + raise + except socket.timeout: + raise + except (socket.error, http.client.HTTPException): + conn.close() + if i == 0: + conn.close() + conn.connect() + continue + else: + raise + else: + content = b"" + if method == "HEAD": + conn.close() + else: + content = response.read() + response = Response(response) + if method != "HEAD": + content = _decompressContent(response, content) + + break + return (response, content) + + def _request( + self, conn, host, absolute_uri, request_uri, method, body, headers, redirections, cachekey, + ): + """Do the actual request using the connection object + and also follow one level of redirects if necessary""" + + auths = [(auth.depth(request_uri), auth) for auth in self.authorizations if auth.inscope(host, request_uri)] + auth = auths and sorted(auths)[0][1] or None + if auth: + auth.request(method, request_uri, headers, body) + + (response, content) = self._conn_request(conn, request_uri, method, body, headers) + + if auth: + if auth.response(response, body): + auth.request(method, request_uri, headers, body) + (response, content) = self._conn_request(conn, request_uri, method, body, headers) + response._stale_digest = 1 + + if response.status == 401: + for authorization in self._auth_from_challenge(host, request_uri, headers, response, content): + authorization.request(method, request_uri, headers, body) + (response, content) = self._conn_request(conn, request_uri, method, body, headers) + if response.status != 401: + self.authorizations.append(authorization) + authorization.response(response, body) + break + + if self.follow_all_redirects or method in self.safe_methods or response.status in (303, 308): + if self.follow_redirects and response.status in self.redirect_codes: + # Pick out the location header and basically start from the beginning + # remembering first to strip the ETag header and decrement our 'depth' + if redirections: + if "location" not in response and response.status != 300: + raise RedirectMissingLocation( + _("Redirected but the response is missing a Location: header."), response, content, + ) + # Fix-up relative redirects (which violate an RFC 2616 MUST) + if "location" in response: + location = response["location"] + (scheme, authority, path, query, fragment) = parse_uri(location) + if authority == None: + response["location"] = urllib.parse.urljoin(absolute_uri, location) + if response.status == 308 or (response.status == 301 and (method in self.safe_methods)): + response["-x-permanent-redirect-url"] = response["location"] + if "content-location" not in response: + response["content-location"] = absolute_uri + _updateCache(headers, response, content, self.cache, cachekey) + if "if-none-match" in headers: + del headers["if-none-match"] + if "if-modified-since" in headers: + del headers["if-modified-since"] + if "authorization" in headers and not self.forward_authorization_headers: + del headers["authorization"] + if "location" in response: + location = response["location"] + old_response = copy.deepcopy(response) + if "content-location" not in old_response: + old_response["content-location"] = absolute_uri + redirect_method = method + if response.status in [302, 303]: + redirect_method = "GET" + body = None + (response, content) = self.request( + location, method=redirect_method, body=body, headers=headers, redirections=redirections - 1, + ) + response.previous = old_response + else: + raise RedirectLimit( + "Redirected more times than redirection_limit allows.", response, content, + ) + elif response.status in [200, 203] and method in self.safe_methods: + # Don't cache 206's since we aren't going to handle byte range requests + if "content-location" not in response: + response["content-location"] = absolute_uri + _updateCache(headers, response, content, self.cache, cachekey) + + return (response, content) + + def _normalize_headers(self, headers): + return _normalize_headers(headers) + + # Need to catch and rebrand some exceptions + # Then need to optionally turn all exceptions into status codes + # including all socket.* and httplib.* exceptions. + + def request( + self, uri, method="GET", body=None, headers=None, redirections=DEFAULT_MAX_REDIRECTS, connection_type=None, + ): + """ Performs a single HTTP request. +The 'uri' is the URI of the HTTP resource and can begin +with either 'http' or 'https'. The value of 'uri' must be an absolute URI. + +The 'method' is the HTTP method to perform, such as GET, POST, DELETE, etc. +There is no restriction on the methods allowed. + +The 'body' is the entity body to be sent with the request. It is a string +object. + +Any extra headers that are to be sent with the request should be provided in the +'headers' dictionary. + +The maximum number of redirect to follow before raising an +exception is 'redirections. The default is 5. + +The return value is a tuple of (response, content), the first +being and instance of the 'Response' class, the second being +a string that contains the response entity body. + """ + conn_key = "" + + try: + if headers is None: + headers = {} + else: + headers = self._normalize_headers(headers) + + if "user-agent" not in headers: + headers["user-agent"] = "Python-httplib2/%s (gzip)" % __version__ + + uri = iri2uri(uri) + # Prevent CWE-75 space injection to manipulate request via part of uri. + # Prevent CWE-93 CRLF injection to modify headers via part of uri. + uri = uri.replace(" ", "%20").replace("\r", "%0D").replace("\n", "%0A") + + (scheme, authority, request_uri, defrag_uri) = urlnorm(uri) + + conn_key = scheme + ":" + authority + conn = self.connections.get(conn_key) + if conn is None: + if not connection_type: + connection_type = SCHEME_TO_CONNECTION[scheme] + certs = list(self.certificates.iter(authority)) + if issubclass(connection_type, HTTPSConnectionWithTimeout): + if certs: + conn = self.connections[conn_key] = connection_type( + authority, + key_file=certs[0][0], + cert_file=certs[0][1], + timeout=self.timeout, + proxy_info=self.proxy_info, + ca_certs=self.ca_certs, + disable_ssl_certificate_validation=self.disable_ssl_certificate_validation, + tls_maximum_version=self.tls_maximum_version, + tls_minimum_version=self.tls_minimum_version, + key_password=certs[0][2], + ) + else: + conn = self.connections[conn_key] = connection_type( + authority, + timeout=self.timeout, + proxy_info=self.proxy_info, + ca_certs=self.ca_certs, + disable_ssl_certificate_validation=self.disable_ssl_certificate_validation, + tls_maximum_version=self.tls_maximum_version, + tls_minimum_version=self.tls_minimum_version, + ) + else: + conn = self.connections[conn_key] = connection_type( + authority, timeout=self.timeout, proxy_info=self.proxy_info + ) + conn.set_debuglevel(debuglevel) + + if "range" not in headers and "accept-encoding" not in headers: + headers["accept-encoding"] = "gzip, deflate" + + info = email.message.Message() + cachekey = None + cached_value = None + if self.cache: + cachekey = defrag_uri + cached_value = self.cache.get(cachekey) + if cached_value: + try: + info, content = cached_value.split(b"\r\n\r\n", 1) + info = email.message_from_bytes(info) + for k, v in info.items(): + if v.startswith("=?") and v.endswith("?="): + info.replace_header(k, str(*email.header.decode_header(v)[0])) + except (IndexError, ValueError): + self.cache.delete(cachekey) + cachekey = None + cached_value = None + + if ( + method in self.optimistic_concurrency_methods + and self.cache + and "etag" in info + and not self.ignore_etag + and "if-match" not in headers + ): + # http://www.w3.org/1999/04/Editing/ + headers["if-match"] = info["etag"] + + # https://tools.ietf.org/html/rfc7234 + # A cache MUST invalidate the effective Request URI as well as [...] Location and Content-Location + # when a non-error status code is received in response to an unsafe request method. + if self.cache and cachekey and method not in self.safe_methods: + self.cache.delete(cachekey) + + # Check the vary header in the cache to see if this request + # matches what varies in the cache. + if method in self.safe_methods and "vary" in info: + vary = info["vary"] + vary_headers = vary.lower().replace(" ", "").split(",") + for header in vary_headers: + key = "-varied-%s" % header + value = info[key] + if headers.get(header, None) != value: + cached_value = None + break + + if ( + self.cache + and cached_value + and (method in self.safe_methods or info["status"] == "308") + and "range" not in headers + ): + redirect_method = method + if info["status"] not in ("307", "308"): + redirect_method = "GET" + if "-x-permanent-redirect-url" in info: + # Should cached permanent redirects be counted in our redirection count? For now, yes. + if redirections <= 0: + raise RedirectLimit( + "Redirected more times than redirection_limit allows.", {}, "", + ) + (response, new_content) = self.request( + info["-x-permanent-redirect-url"], + method=redirect_method, + headers=headers, + redirections=redirections - 1, + ) + response.previous = Response(info) + response.previous.fromcache = True + else: + # Determine our course of action: + # Is the cached entry fresh or stale? + # Has the client requested a non-cached response? + # + # There seems to be three possible answers: + # 1. [FRESH] Return the cache entry w/o doing a GET + # 2. [STALE] Do the GET (but add in cache validators if available) + # 3. [TRANSPARENT] Do a GET w/o any cache validators (Cache-Control: no-cache) on the request + entry_disposition = _entry_disposition(info, headers) + + if entry_disposition == "FRESH": + response = Response(info) + response.fromcache = True + return (response, content) + + if entry_disposition == "STALE": + if "etag" in info and not self.ignore_etag and not "if-none-match" in headers: + headers["if-none-match"] = info["etag"] + if "last-modified" in info and not "last-modified" in headers: + headers["if-modified-since"] = info["last-modified"] + elif entry_disposition == "TRANSPARENT": + pass + + (response, new_content) = self._request( + conn, authority, uri, request_uri, method, body, headers, redirections, cachekey, + ) + + if response.status == 304 and method == "GET": + # Rewrite the cache entry with the new end-to-end headers + # Take all headers that are in response + # and overwrite their values in info. + # unless they are hop-by-hop, or are listed in the connection header. + + for key in _get_end2end_headers(response): + info[key] = response[key] + merged_response = Response(info) + if hasattr(response, "_stale_digest"): + merged_response._stale_digest = response._stale_digest + _updateCache(headers, merged_response, content, self.cache, cachekey) + response = merged_response + response.status = 200 + response.fromcache = True + + elif response.status == 200: + content = new_content + else: + self.cache.delete(cachekey) + content = new_content + else: + cc = _parse_cache_control(headers) + if "only-if-cached" in cc: + info["status"] = "504" + response = Response(info) + content = b"" + else: + (response, content) = self._request( + conn, authority, uri, request_uri, method, body, headers, redirections, cachekey, + ) + except Exception as e: + is_timeout = isinstance(e, socket.timeout) + if is_timeout: + conn = self.connections.pop(conn_key, None) + if conn: + conn.close() + + if self.force_exception_to_status_code: + if isinstance(e, HttpLib2ErrorWithResponse): + response = e.response + content = e.content + response.status = 500 + response.reason = str(e) + elif isinstance(e, socket.timeout): + content = b"Request Timeout" + response = Response({"content-type": "text/plain", "status": "408", "content-length": len(content),}) + response.reason = "Request Timeout" + else: + content = str(e).encode("utf-8") + response = Response({"content-type": "text/plain", "status": "400", "content-length": len(content),}) + response.reason = "Bad Request" + else: + raise + + return (response, content) + + +class Response(dict): + """An object more like email.message than httplib.HTTPResponse.""" + + """Is this response from our local cache""" + fromcache = False + """HTTP protocol version used by server. + + 10 for HTTP/1.0, 11 for HTTP/1.1. + """ + version = 11 + + "Status code returned by server. " + status = 200 + """Reason phrase returned by server.""" + reason = "Ok" + + previous = None + + def __init__(self, info): + # info is either an email.message or + # an httplib.HTTPResponse object. + if isinstance(info, http.client.HTTPResponse): + for key, value in info.getheaders(): + key = key.lower() + prev = self.get(key) + if prev is not None: + value = ", ".join((prev, value)) + self[key] = value + self.status = info.status + self["status"] = str(self.status) + self.reason = info.reason + self.version = info.version + elif isinstance(info, email.message.Message): + for key, value in list(info.items()): + self[key.lower()] = value + self.status = int(self["status"]) + else: + for key, value in info.items(): + self[key.lower()] = value + self.status = int(self.get("status", self.status)) + + def __getattr__(self, name): + if name == "dict": + return self + else: + raise AttributeError(name) diff --git a/shotgun_api3/lib/httplib2/auth.py b/shotgun_api3/lib/httplib2/auth.py index 53f427be1..fd75e61bd 100644 --- a/shotgun_api3/lib/httplib2/auth.py +++ b/shotgun_api3/lib/httplib2/auth.py @@ -1,7 +1,7 @@ import base64 import re -from ... import pyparsing as pp +from .. import pyparsing as pp from .error import * diff --git a/shotgun_api3/lib/httplib2/python2/__init__.py b/shotgun_api3/lib/httplib2/python2/__init__.py deleted file mode 100644 index cbd8f382e..000000000 --- a/shotgun_api3/lib/httplib2/python2/__init__.py +++ /dev/null @@ -1,1993 +0,0 @@ -"""Small, fast HTTP client library for Python. - -Features persistent connections, cache, and Google App Engine Standard -Environment support. -""" - -from __future__ import print_function - -__author__ = "Joe Gregorio (joe@bitworking.org)" -__copyright__ = "Copyright 2006, Joe Gregorio" -__contributors__ = [ - "Thomas Broyer (t.broyer@ltgt.net)", - "James Antill", - "Xavier Verges Farrero", - "Jonathan Feinberg", - "Blair Zajac", - "Sam Ruby", - "Louis Nyffenegger", - "Alex Yu", - "Lai Han", -] -__license__ = "MIT" -__version__ = "0.22.0" - -import base64 -import calendar -import copy -import email -import email.FeedParser -import email.Message -import email.Utils -import errno -import gzip -import httplib -import os -import random -import re -import StringIO -import sys -import time -import urllib -import urlparse -import zlib - -try: - from hashlib import sha1 as _sha, md5 as _md5 -except ImportError: - # prior to Python 2.5, these were separate modules - import sha - import md5 - - _sha = sha.new - _md5 = md5.new -import hmac -from gettext import gettext as _ -import socket - -try: - from . import socks -except ImportError: - try: - import socks - except (ImportError, AttributeError): - socks = None -from . import auth -from .error import * - -# Build the appropriate socket wrapper for ssl -ssl = None -ssl_SSLError = None -ssl_CertificateError = None -try: - import ssl # python 2.6 -except ImportError: - pass -if ssl is not None: - ssl_SSLError = getattr(ssl, "SSLError", None) - ssl_CertificateError = getattr(ssl, "CertificateError", None) - - -def _ssl_wrap_socket(sock, key_file, cert_file, disable_validation, ca_certs, ssl_version, hostname, key_password): - if disable_validation: - cert_reqs = ssl.CERT_NONE - else: - cert_reqs = ssl.CERT_REQUIRED - if ssl_version is None: - ssl_version = ssl.PROTOCOL_SSLv23 - - if hasattr(ssl, "SSLContext"): # Python 2.7.9 - context = ssl.SSLContext(ssl_version) - context.verify_mode = cert_reqs - context.check_hostname = cert_reqs != ssl.CERT_NONE - if cert_file: - if key_password: - context.load_cert_chain(cert_file, key_file, key_password) - else: - context.load_cert_chain(cert_file, key_file) - if ca_certs: - context.load_verify_locations(ca_certs) - return context.wrap_socket(sock, server_hostname=hostname) - else: - if key_password: - raise NotSupportedOnThisPlatform("Certificate with password is not supported.") - return ssl.wrap_socket( - sock, keyfile=key_file, certfile=cert_file, cert_reqs=cert_reqs, ca_certs=ca_certs, ssl_version=ssl_version, - ) - - -def _ssl_wrap_socket_unsupported( - sock, key_file, cert_file, disable_validation, ca_certs, ssl_version, hostname, key_password -): - if not disable_validation: - raise CertificateValidationUnsupported( - "SSL certificate validation is not supported without " - "the ssl module installed. To avoid this error, install " - "the ssl module, or explicity disable validation." - ) - if key_password: - raise NotSupportedOnThisPlatform("Certificate with password is not supported.") - ssl_sock = socket.ssl(sock, key_file, cert_file) - return httplib.FakeSocket(sock, ssl_sock) - - -if ssl is None: - _ssl_wrap_socket = _ssl_wrap_socket_unsupported - -if sys.version_info >= (2, 3): - from .iri2uri import iri2uri -else: - - def iri2uri(uri): - return uri - - -def has_timeout(timeout): # python 2.6 - if hasattr(socket, "_GLOBAL_DEFAULT_TIMEOUT"): - return timeout is not None and timeout is not socket._GLOBAL_DEFAULT_TIMEOUT - return timeout is not None - - -__all__ = [ - "Http", - "Response", - "ProxyInfo", - "HttpLib2Error", - "RedirectMissingLocation", - "RedirectLimit", - "FailedToDecompressContent", - "UnimplementedDigestAuthOptionError", - "UnimplementedHmacDigestAuthOptionError", - "debuglevel", - "ProxiesUnavailableError", -] - -# The httplib debug level, set to a non-zero value to get debug output -debuglevel = 0 - -# A request will be tried 'RETRIES' times if it fails at the socket/connection level. -RETRIES = 2 - -# Python 2.3 support -if sys.version_info < (2, 4): - - def sorted(seq): - seq.sort() - return seq - - -# Python 2.3 support -def HTTPResponse__getheaders(self): - """Return list of (header, value) tuples.""" - if self.msg is None: - raise httplib.ResponseNotReady() - return self.msg.items() - - -if not hasattr(httplib.HTTPResponse, "getheaders"): - httplib.HTTPResponse.getheaders = HTTPResponse__getheaders - - -# All exceptions raised here derive from HttpLib2Error -class HttpLib2Error(Exception): - pass - - -# Some exceptions can be caught and optionally -# be turned back into responses. -class HttpLib2ErrorWithResponse(HttpLib2Error): - def __init__(self, desc, response, content): - self.response = response - self.content = content - HttpLib2Error.__init__(self, desc) - - -class RedirectMissingLocation(HttpLib2ErrorWithResponse): - pass - - -class RedirectLimit(HttpLib2ErrorWithResponse): - pass - - -class FailedToDecompressContent(HttpLib2ErrorWithResponse): - pass - - -class UnimplementedDigestAuthOptionError(HttpLib2ErrorWithResponse): - pass - - -class UnimplementedHmacDigestAuthOptionError(HttpLib2ErrorWithResponse): - pass - - -class MalformedHeader(HttpLib2Error): - pass - - -class RelativeURIError(HttpLib2Error): - pass - - -class ServerNotFoundError(HttpLib2Error): - pass - - -class ProxiesUnavailableError(HttpLib2Error): - pass - - -class CertificateValidationUnsupported(HttpLib2Error): - pass - - -class SSLHandshakeError(HttpLib2Error): - pass - - -class NotSupportedOnThisPlatform(HttpLib2Error): - pass - - -class CertificateHostnameMismatch(SSLHandshakeError): - def __init__(self, desc, host, cert): - HttpLib2Error.__init__(self, desc) - self.host = host - self.cert = cert - - -class NotRunningAppEngineEnvironment(HttpLib2Error): - pass - - -# Open Items: -# ----------- -# Proxy support - -# Are we removing the cached content too soon on PUT (only delete on 200 Maybe?) - -# Pluggable cache storage (supports storing the cache in -# flat files by default. We need a plug-in architecture -# that can support Berkeley DB and Squid) - -# == Known Issues == -# Does not handle a resource that uses conneg and Last-Modified but no ETag as a cache validator. -# Does not handle Cache-Control: max-stale -# Does not use Age: headers when calculating cache freshness. - -# The number of redirections to follow before giving up. -# Note that only GET redirects are automatically followed. -# Will also honor 301 requests by saving that info and never -# requesting that URI again. -DEFAULT_MAX_REDIRECTS = 5 - -from . import certs - -CA_CERTS = certs.where() - -# Which headers are hop-by-hop headers by default -HOP_BY_HOP = [ - "connection", - "keep-alive", - "proxy-authenticate", - "proxy-authorization", - "te", - "trailers", - "transfer-encoding", - "upgrade", -] - -# https://tools.ietf.org/html/rfc7231#section-8.1.3 -SAFE_METHODS = ("GET", "HEAD") # TODO add "OPTIONS", "TRACE" - -# To change, assign to `Http().redirect_codes` -REDIRECT_CODES = frozenset((300, 301, 302, 303, 307, 308)) - - -def _get_end2end_headers(response): - hopbyhop = list(HOP_BY_HOP) - hopbyhop.extend([x.strip() for x in response.get("connection", "").split(",")]) - return [header for header in response.keys() if header not in hopbyhop] - - -URI = re.compile(r"^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?") - - -def parse_uri(uri): - """Parses a URI using the regex given in Appendix B of RFC 3986. - - (scheme, authority, path, query, fragment) = parse_uri(uri) - """ - groups = URI.match(uri).groups() - return (groups[1], groups[3], groups[4], groups[6], groups[8]) - - -def urlnorm(uri): - (scheme, authority, path, query, fragment) = parse_uri(uri) - if not scheme or not authority: - raise RelativeURIError("Only absolute URIs are allowed. uri = %s" % uri) - authority = authority.lower() - scheme = scheme.lower() - if not path: - path = "/" - # Could do syntax based normalization of the URI before - # computing the digest. See Section 6.2.2 of Std 66. - request_uri = query and "?".join([path, query]) or path - scheme = scheme.lower() - defrag_uri = scheme + "://" + authority + request_uri - return scheme, authority, request_uri, defrag_uri - - -# Cache filename construction (original borrowed from Venus http://intertwingly.net/code/venus/) -re_url_scheme = re.compile(r"^\w+://") -re_unsafe = re.compile(r"[^\w\-_.()=!]+") - - -def safename(filename): - """Return a filename suitable for the cache. - Strips dangerous and common characters to create a filename we - can use to store the cache in. - """ - if isinstance(filename, str): - filename_bytes = filename - filename = filename.decode("utf-8") - else: - filename_bytes = filename.encode("utf-8") - filemd5 = _md5(filename_bytes).hexdigest() - filename = re_url_scheme.sub("", filename) - filename = re_unsafe.sub("", filename) - - # limit length of filename (vital for Windows) - # https://github.com/httplib2/httplib2/pull/74 - # C:\Users\ \AppData\Local\Temp\ , - # 9 chars + max 104 chars + 20 chars + x + 1 + 32 = max 259 chars - # Thus max safe filename x = 93 chars. Let it be 90 to make a round sum: - filename = filename[:90] - - return ",".join((filename, filemd5)) - - -NORMALIZE_SPACE = re.compile(r"(?:\r\n)?[ \t]+") - - -def _normalize_headers(headers): - return dict([(key.lower(), NORMALIZE_SPACE.sub(value, " ").strip()) for (key, value) in headers.iteritems()]) - - -def _parse_cache_control(headers): - retval = {} - if "cache-control" in headers: - parts = headers["cache-control"].split(",") - parts_with_args = [ - tuple([x.strip().lower() for x in part.split("=", 1)]) for part in parts if -1 != part.find("=") - ] - parts_wo_args = [(name.strip().lower(), 1) for name in parts if -1 == name.find("=")] - retval = dict(parts_with_args + parts_wo_args) - return retval - - -# Whether to use a strict mode to parse WWW-Authenticate headers -# Might lead to bad results in case of ill-formed header value, -# so disabled by default, falling back to relaxed parsing. -# Set to true to turn on, usefull for testing servers. -USE_WWW_AUTH_STRICT_PARSING = 0 - - -# TODO: add current time as _entry_disposition argument to avoid sleep in tests -def _entry_disposition(response_headers, request_headers): - """Determine freshness from the Date, Expires and Cache-Control headers. - - We don't handle the following: - - 1. Cache-Control: max-stale - 2. Age: headers are not used in the calculations. - - Not that this algorithm is simpler than you might think - because we are operating as a private (non-shared) cache. - This lets us ignore 's-maxage'. We can also ignore - 'proxy-invalidate' since we aren't a proxy. - We will never return a stale document as - fresh as a design decision, and thus the non-implementation - of 'max-stale'. This also lets us safely ignore 'must-revalidate' - since we operate as if every server has sent 'must-revalidate'. - Since we are private we get to ignore both 'public' and - 'private' parameters. We also ignore 'no-transform' since - we don't do any transformations. - The 'no-store' parameter is handled at a higher level. - So the only Cache-Control parameters we look at are: - - no-cache - only-if-cached - max-age - min-fresh - """ - - retval = "STALE" - cc = _parse_cache_control(request_headers) - cc_response = _parse_cache_control(response_headers) - - if "pragma" in request_headers and request_headers["pragma"].lower().find("no-cache") != -1: - retval = "TRANSPARENT" - if "cache-control" not in request_headers: - request_headers["cache-control"] = "no-cache" - elif "no-cache" in cc: - retval = "TRANSPARENT" - elif "no-cache" in cc_response: - retval = "STALE" - elif "only-if-cached" in cc: - retval = "FRESH" - elif "date" in response_headers: - date = calendar.timegm(email.Utils.parsedate_tz(response_headers["date"])) - now = time.time() - current_age = max(0, now - date) - if "max-age" in cc_response: - try: - freshness_lifetime = int(cc_response["max-age"]) - except ValueError: - freshness_lifetime = 0 - elif "expires" in response_headers: - expires = email.Utils.parsedate_tz(response_headers["expires"]) - if None == expires: - freshness_lifetime = 0 - else: - freshness_lifetime = max(0, calendar.timegm(expires) - date) - else: - freshness_lifetime = 0 - if "max-age" in cc: - try: - freshness_lifetime = int(cc["max-age"]) - except ValueError: - freshness_lifetime = 0 - if "min-fresh" in cc: - try: - min_fresh = int(cc["min-fresh"]) - except ValueError: - min_fresh = 0 - current_age += min_fresh - if freshness_lifetime > current_age: - retval = "FRESH" - return retval - - -def _decompressContent(response, new_content): - content = new_content - try: - encoding = response.get("content-encoding", None) - if encoding in ["gzip", "deflate"]: - if encoding == "gzip": - content = gzip.GzipFile(fileobj=StringIO.StringIO(new_content)).read() - if encoding == "deflate": - try: - content = zlib.decompress(content, zlib.MAX_WBITS) - except (IOError, zlib.error): - content = zlib.decompress(content, -zlib.MAX_WBITS) - response["content-length"] = str(len(content)) - # Record the historical presence of the encoding in a way the won't interfere. - response["-content-encoding"] = response["content-encoding"] - del response["content-encoding"] - except (IOError, zlib.error): - content = "" - raise FailedToDecompressContent( - _("Content purported to be compressed with %s but failed to decompress.") % response.get("content-encoding"), - response, - content, - ) - return content - - -def _updateCache(request_headers, response_headers, content, cache, cachekey): - if cachekey: - cc = _parse_cache_control(request_headers) - cc_response = _parse_cache_control(response_headers) - if "no-store" in cc or "no-store" in cc_response: - cache.delete(cachekey) - else: - info = email.Message.Message() - for key, value in response_headers.iteritems(): - if key not in ["status", "content-encoding", "transfer-encoding"]: - info[key] = value - - # Add annotations to the cache to indicate what headers - # are variant for this request. - vary = response_headers.get("vary", None) - if vary: - vary_headers = vary.lower().replace(" ", "").split(",") - for header in vary_headers: - key = "-varied-%s" % header - try: - info[key] = request_headers[header] - except KeyError: - pass - - status = response_headers.status - if status == 304: - status = 200 - - status_header = "status: %d\r\n" % status - - header_str = info.as_string() - - header_str = re.sub("\r(?!\n)|(? 0: - service = "cl" - # No point in guessing Base or Spreadsheet - # elif request_uri.find("spreadsheets") > 0: - # service = "wise" - - auth = dict(Email=credentials[0], Passwd=credentials[1], service=service, source=headers["user-agent"],) - resp, content = self.http.request( - "https://www.google.com/accounts/ClientLogin", - method="POST", - body=urlencode(auth), - headers={"Content-Type": "application/x-www-form-urlencoded"}, - ) - lines = content.split("\n") - d = dict([tuple(line.split("=", 1)) for line in lines if line]) - if resp.status == 403: - self.Auth = "" - else: - self.Auth = d["Auth"] - - def request(self, method, request_uri, headers, content): - """Modify the request headers to add the appropriate - Authorization header.""" - headers["authorization"] = "GoogleLogin Auth=" + self.Auth - - -AUTH_SCHEME_CLASSES = { - "basic": BasicAuthentication, - "wsse": WsseAuthentication, - "digest": DigestAuthentication, - "hmacdigest": HmacDigestAuthentication, - "googlelogin": GoogleLoginAuthentication, -} - -AUTH_SCHEME_ORDER = ["hmacdigest", "googlelogin", "digest", "wsse", "basic"] - - -class FileCache(object): - """Uses a local directory as a store for cached files. - Not really safe to use if multiple threads or processes are going to - be running on the same cache. - """ - - def __init__(self, cache, safe=safename): # use safe=lambda x: md5.new(x).hexdigest() for the old behavior - self.cache = cache - self.safe = safe - if not os.path.exists(cache): - os.makedirs(self.cache) - - def get(self, key): - retval = None - cacheFullPath = os.path.join(self.cache, self.safe(key)) - try: - f = file(cacheFullPath, "rb") - retval = f.read() - f.close() - except IOError: - pass - return retval - - def set(self, key, value): - cacheFullPath = os.path.join(self.cache, self.safe(key)) - f = file(cacheFullPath, "wb") - f.write(value) - f.close() - - def delete(self, key): - cacheFullPath = os.path.join(self.cache, self.safe(key)) - if os.path.exists(cacheFullPath): - os.remove(cacheFullPath) - - -class Credentials(object): - def __init__(self): - self.credentials = [] - - def add(self, name, password, domain=""): - self.credentials.append((domain.lower(), name, password)) - - def clear(self): - self.credentials = [] - - def iter(self, domain): - for (cdomain, name, password) in self.credentials: - if cdomain == "" or domain == cdomain: - yield (name, password) - - -class KeyCerts(Credentials): - """Identical to Credentials except that - name/password are mapped to key/cert.""" - - def add(self, key, cert, domain, password): - self.credentials.append((domain.lower(), key, cert, password)) - - def iter(self, domain): - for (cdomain, key, cert, password) in self.credentials: - if cdomain == "" or domain == cdomain: - yield (key, cert, password) - - -class AllHosts(object): - pass - - -class ProxyInfo(object): - """Collect information required to use a proxy.""" - - bypass_hosts = () - - def __init__( - self, proxy_type, proxy_host, proxy_port, proxy_rdns=True, proxy_user=None, proxy_pass=None, proxy_headers=None, - ): - """Args: - - proxy_type: The type of proxy server. This must be set to one of - socks.PROXY_TYPE_XXX constants. For example: p = - ProxyInfo(proxy_type=socks.PROXY_TYPE_HTTP, proxy_host='localhost', - proxy_port=8000) - proxy_host: The hostname or IP address of the proxy server. - proxy_port: The port that the proxy server is running on. - proxy_rdns: If True (default), DNS queries will not be performed - locally, and instead, handed to the proxy to resolve. This is useful - if the network does not allow resolution of non-local names. In - httplib2 0.9 and earlier, this defaulted to False. - proxy_user: The username used to authenticate with the proxy server. - proxy_pass: The password used to authenticate with the proxy server. - proxy_headers: Additional or modified headers for the proxy connect - request. - """ - self.proxy_type = proxy_type - self.proxy_host = proxy_host - self.proxy_port = proxy_port - self.proxy_rdns = proxy_rdns - self.proxy_user = proxy_user - self.proxy_pass = proxy_pass - self.proxy_headers = proxy_headers - - def astuple(self): - return ( - self.proxy_type, - self.proxy_host, - self.proxy_port, - self.proxy_rdns, - self.proxy_user, - self.proxy_pass, - self.proxy_headers, - ) - - def isgood(self): - return (self.proxy_host != None) and (self.proxy_port != None) - - def applies_to(self, hostname): - return not self.bypass_host(hostname) - - def bypass_host(self, hostname): - """Has this host been excluded from the proxy config""" - if self.bypass_hosts is AllHosts: - return True - - hostname = "." + hostname.lstrip(".") - for skip_name in self.bypass_hosts: - # *.suffix - if skip_name.startswith(".") and hostname.endswith(skip_name): - return True - # exact match - if hostname == "." + skip_name: - return True - return False - - def __repr__(self): - return ( - "" - ).format(p=self) - - -def proxy_info_from_environment(method="http"): - """Read proxy info from the environment variables. - """ - if method not in ["http", "https"]: - return - - env_var = method + "_proxy" - url = os.environ.get(env_var, os.environ.get(env_var.upper())) - if not url: - return - return proxy_info_from_url(url, method, None) - - -def proxy_info_from_url(url, method="http", noproxy=None): - """Construct a ProxyInfo from a URL (such as http_proxy env var) - """ - url = urlparse.urlparse(url) - - proxy_type = 3 # socks.PROXY_TYPE_HTTP - pi = ProxyInfo( - proxy_type=proxy_type, - proxy_host=url.hostname, - proxy_port=url.port or dict(https=443, http=80)[method], - proxy_user=url.username or None, - proxy_pass=url.password or None, - proxy_headers=None, - ) - - bypass_hosts = [] - # If not given an explicit noproxy value, respect values in env vars. - if noproxy is None: - noproxy = os.environ.get("no_proxy", os.environ.get("NO_PROXY", "")) - # Special case: A single '*' character means all hosts should be bypassed. - if noproxy == "*": - bypass_hosts = AllHosts - elif noproxy.strip(): - bypass_hosts = noproxy.split(",") - bypass_hosts = filter(bool, bypass_hosts) # To exclude empty string. - - pi.bypass_hosts = bypass_hosts - return pi - - -class HTTPConnectionWithTimeout(httplib.HTTPConnection): - """HTTPConnection subclass that supports timeouts - - All timeouts are in seconds. If None is passed for timeout then - Python's default timeout for sockets will be used. See for example - the docs of socket.setdefaulttimeout(): - http://docs.python.org/library/socket.html#socket.setdefaulttimeout - """ - - def __init__(self, host, port=None, strict=None, timeout=None, proxy_info=None): - httplib.HTTPConnection.__init__(self, host, port, strict) - self.timeout = timeout - self.proxy_info = proxy_info - - def connect(self): - """Connect to the host and port specified in __init__.""" - # Mostly verbatim from httplib.py. - if self.proxy_info and socks is None: - raise ProxiesUnavailableError("Proxy support missing but proxy use was requested!") - if self.proxy_info and self.proxy_info.isgood(): - use_proxy = True - ( - proxy_type, - proxy_host, - proxy_port, - proxy_rdns, - proxy_user, - proxy_pass, - proxy_headers, - ) = self.proxy_info.astuple() - - host = proxy_host - port = proxy_port - else: - use_proxy = False - - host = self.host - port = self.port - - socket_err = None - - for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM): - af, socktype, proto, canonname, sa = res - try: - if use_proxy: - self.sock = socks.socksocket(af, socktype, proto) - self.sock.setproxy( - proxy_type, proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers, - ) - else: - self.sock = socket.socket(af, socktype, proto) - self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) - # Different from httplib: support timeouts. - if has_timeout(self.timeout): - self.sock.settimeout(self.timeout) - # End of difference from httplib. - if self.debuglevel > 0: - print("connect: (%s, %s) ************" % (self.host, self.port)) - if use_proxy: - print( - "proxy: %s ************" - % str((proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers,)) - ) - if use_proxy: - self.sock.connect((self.host, self.port) + sa[2:]) - else: - self.sock.connect(sa) - except socket.error as e: - socket_err = e - if self.debuglevel > 0: - print("connect fail: (%s, %s)" % (self.host, self.port)) - if use_proxy: - print( - "proxy: %s" - % str((proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers,)) - ) - if self.sock: - self.sock.close() - self.sock = None - continue - break - if not self.sock: - raise socket_err or socket.error("getaddrinfo returns an empty list") - - -class HTTPSConnectionWithTimeout(httplib.HTTPSConnection): - """This class allows communication via SSL. - - All timeouts are in seconds. If None is passed for timeout then - Python's default timeout for sockets will be used. See for example - the docs of socket.setdefaulttimeout(): - http://docs.python.org/library/socket.html#socket.setdefaulttimeout - """ - - def __init__( - self, - host, - port=None, - key_file=None, - cert_file=None, - strict=None, - timeout=None, - proxy_info=None, - ca_certs=None, - disable_ssl_certificate_validation=False, - ssl_version=None, - key_password=None, - ): - if key_password: - httplib.HTTPSConnection.__init__(self, host, port=port, strict=strict) - self._context.load_cert_chain(cert_file, key_file, key_password) - self.key_file = key_file - self.cert_file = cert_file - self.key_password = key_password - else: - httplib.HTTPSConnection.__init__( - self, host, port=port, key_file=key_file, cert_file=cert_file, strict=strict - ) - self.key_password = None - self.timeout = timeout - self.proxy_info = proxy_info - if ca_certs is None: - ca_certs = CA_CERTS - self.ca_certs = ca_certs - self.disable_ssl_certificate_validation = disable_ssl_certificate_validation - self.ssl_version = ssl_version - - # The following two methods were adapted from https_wrapper.py, released - # with the Google Appengine SDK at - # http://googleappengine.googlecode.com/svn-history/r136/trunk/python/google/appengine/tools/https_wrapper.py - # under the following license: - # - # Copyright 2007 Google Inc. - # - # Licensed under the Apache License, Version 2.0 (the "License"); - # you may not use this file except in compliance with the License. - # You may obtain a copy of the License at - # - # http://www.apache.org/licenses/LICENSE-2.0 - # - # Unless required by applicable law or agreed to in writing, software - # distributed under the License is distributed on an "AS IS" BASIS, - # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - # See the License for the specific language governing permissions and - # limitations under the License. - # - - def _GetValidHostsForCert(self, cert): - """Returns a list of valid host globs for an SSL certificate. - - Args: - cert: A dictionary representing an SSL certificate. - Returns: - list: A list of valid host globs. - """ - if "subjectAltName" in cert: - return [x[1] for x in cert["subjectAltName"] if x[0].lower() == "dns"] - else: - return [x[0][1] for x in cert["subject"] if x[0][0].lower() == "commonname"] - - def _ValidateCertificateHostname(self, cert, hostname): - """Validates that a given hostname is valid for an SSL certificate. - - Args: - cert: A dictionary representing an SSL certificate. - hostname: The hostname to test. - Returns: - bool: Whether or not the hostname is valid for this certificate. - """ - hosts = self._GetValidHostsForCert(cert) - for host in hosts: - host_re = host.replace(".", "\.").replace("*", "[^.]*") - if re.search("^%s$" % (host_re,), hostname, re.I): - return True - return False - - def connect(self): - "Connect to a host on a given (SSL) port." - - if self.proxy_info and self.proxy_info.isgood(): - use_proxy = True - ( - proxy_type, - proxy_host, - proxy_port, - proxy_rdns, - proxy_user, - proxy_pass, - proxy_headers, - ) = self.proxy_info.astuple() - - host = proxy_host - port = proxy_port - else: - use_proxy = False - - host = self.host - port = self.port - - socket_err = None - - address_info = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM) - for family, socktype, proto, canonname, sockaddr in address_info: - try: - if use_proxy: - sock = socks.socksocket(family, socktype, proto) - - sock.setproxy( - proxy_type, proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers, - ) - else: - sock = socket.socket(family, socktype, proto) - sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) - - if has_timeout(self.timeout): - sock.settimeout(self.timeout) - - if use_proxy: - sock.connect((self.host, self.port) + sockaddr[:2]) - else: - sock.connect(sockaddr) - self.sock = _ssl_wrap_socket( - sock, - self.key_file, - self.cert_file, - self.disable_ssl_certificate_validation, - self.ca_certs, - self.ssl_version, - self.host, - self.key_password, - ) - if self.debuglevel > 0: - print("connect: (%s, %s)" % (self.host, self.port)) - if use_proxy: - print( - "proxy: %s" - % str((proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers,)) - ) - if not self.disable_ssl_certificate_validation: - cert = self.sock.getpeercert() - hostname = self.host.split(":", 0)[0] - if not self._ValidateCertificateHostname(cert, hostname): - raise CertificateHostnameMismatch( - "Server presented certificate that does not match " "host %s: %s" % (hostname, cert), - hostname, - cert, - ) - except (ssl_SSLError, ssl_CertificateError, CertificateHostnameMismatch,) as e: - if sock: - sock.close() - if self.sock: - self.sock.close() - self.sock = None - # Unfortunately the ssl module doesn't seem to provide any way - # to get at more detailed error information, in particular - # whether the error is due to certificate validation or - # something else (such as SSL protocol mismatch). - if getattr(e, "errno", None) == ssl.SSL_ERROR_SSL: - raise SSLHandshakeError(e) - else: - raise - except (socket.timeout, socket.gaierror): - raise - except socket.error as e: - socket_err = e - if self.debuglevel > 0: - print("connect fail: (%s, %s)" % (self.host, self.port)) - if use_proxy: - print( - "proxy: %s" - % str((proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers,)) - ) - if self.sock: - self.sock.close() - self.sock = None - continue - break - if not self.sock: - raise socket_err or socket.error("getaddrinfo returns an empty list") - - -SCHEME_TO_CONNECTION = { - "http": HTTPConnectionWithTimeout, - "https": HTTPSConnectionWithTimeout, -} - - -def _new_fixed_fetch(validate_certificate): - def fixed_fetch( - url, payload=None, method="GET", headers={}, allow_truncated=False, follow_redirects=True, deadline=None, - ): - return fetch( - url, - payload=payload, - method=method, - headers=headers, - allow_truncated=allow_truncated, - follow_redirects=follow_redirects, - deadline=deadline, - validate_certificate=validate_certificate, - ) - - return fixed_fetch - - -class AppEngineHttpConnection(httplib.HTTPConnection): - """Use httplib on App Engine, but compensate for its weirdness. - - The parameters key_file, cert_file, proxy_info, ca_certs, - disable_ssl_certificate_validation, and ssl_version are all dropped on - the ground. - """ - - def __init__( - self, - host, - port=None, - key_file=None, - cert_file=None, - strict=None, - timeout=None, - proxy_info=None, - ca_certs=None, - disable_ssl_certificate_validation=False, - ssl_version=None, - ): - httplib.HTTPConnection.__init__(self, host, port=port, strict=strict, timeout=timeout) - - -class AppEngineHttpsConnection(httplib.HTTPSConnection): - """Same as AppEngineHttpConnection, but for HTTPS URIs. - - The parameters proxy_info, ca_certs, disable_ssl_certificate_validation, - and ssl_version are all dropped on the ground. - """ - - def __init__( - self, - host, - port=None, - key_file=None, - cert_file=None, - strict=None, - timeout=None, - proxy_info=None, - ca_certs=None, - disable_ssl_certificate_validation=False, - ssl_version=None, - key_password=None, - ): - if key_password: - raise NotSupportedOnThisPlatform("Certificate with password is not supported.") - httplib.HTTPSConnection.__init__( - self, host, port=port, key_file=key_file, cert_file=cert_file, strict=strict, timeout=timeout, - ) - self._fetch = _new_fixed_fetch(not disable_ssl_certificate_validation) - - -# Use a different connection object for Google App Engine Standard Environment. -def is_gae_instance(): - server_software = os.environ.get("SERVER_SOFTWARE", "") - if ( - server_software.startswith("Google App Engine/") - or server_software.startswith("Development/") - or server_software.startswith("testutil/") - ): - return True - return False - - -try: - if not is_gae_instance(): - raise NotRunningAppEngineEnvironment() - - from google.appengine.api import apiproxy_stub_map - - if apiproxy_stub_map.apiproxy.GetStub("urlfetch") is None: - raise ImportError - - from google.appengine.api.urlfetch import fetch - - # Update the connection classes to use the Googel App Engine specific ones. - SCHEME_TO_CONNECTION = { - "http": AppEngineHttpConnection, - "https": AppEngineHttpsConnection, - } -except (ImportError, NotRunningAppEngineEnvironment): - pass - - -class Http(object): - """An HTTP client that handles: - - - all methods - - caching - - ETags - - compression, - - HTTPS - - Basic - - Digest - - WSSE - - and more. - """ - - def __init__( - self, - cache=None, - timeout=None, - proxy_info=proxy_info_from_environment, - ca_certs=None, - disable_ssl_certificate_validation=False, - ssl_version=None, - ): - """If 'cache' is a string then it is used as a directory name for - a disk cache. Otherwise it must be an object that supports the - same interface as FileCache. - - All timeouts are in seconds. If None is passed for timeout - then Python's default timeout for sockets will be used. See - for example the docs of socket.setdefaulttimeout(): - http://docs.python.org/library/socket.html#socket.setdefaulttimeout - - `proxy_info` may be: - - a callable that takes the http scheme ('http' or 'https') and - returns a ProxyInfo instance per request. By default, uses - proxy_nfo_from_environment. - - a ProxyInfo instance (static proxy config). - - None (proxy disabled). - - ca_certs is the path of a file containing root CA certificates for SSL - server certificate validation. By default, a CA cert file bundled with - httplib2 is used. - - If disable_ssl_certificate_validation is true, SSL cert validation will - not be performed. - - By default, ssl.PROTOCOL_SSLv23 will be used for the ssl version. - """ - self.proxy_info = proxy_info - self.ca_certs = ca_certs - self.disable_ssl_certificate_validation = disable_ssl_certificate_validation - self.ssl_version = ssl_version - - # Map domain name to an httplib connection - self.connections = {} - # The location of the cache, for now a directory - # where cached responses are held. - if cache and isinstance(cache, basestring): - self.cache = FileCache(cache) - else: - self.cache = cache - - # Name/password - self.credentials = Credentials() - - # Key/cert - self.certificates = KeyCerts() - - # authorization objects - self.authorizations = [] - - # If set to False then no redirects are followed, even safe ones. - self.follow_redirects = True - - self.redirect_codes = REDIRECT_CODES - - # Which HTTP methods do we apply optimistic concurrency to, i.e. - # which methods get an "if-match:" etag header added to them. - self.optimistic_concurrency_methods = ["PUT", "PATCH"] - - self.safe_methods = list(SAFE_METHODS) - - # If 'follow_redirects' is True, and this is set to True then - # all redirecs are followed, including unsafe ones. - self.follow_all_redirects = False - - self.ignore_etag = False - - self.force_exception_to_status_code = False - - self.timeout = timeout - - # Keep Authorization: headers on a redirect. - self.forward_authorization_headers = False - - def close(self): - """Close persistent connections, clear sensitive data. - Not thread-safe, requires external synchronization against concurrent requests. - """ - existing, self.connections = self.connections, {} - for _, c in existing.iteritems(): - c.close() - self.certificates.clear() - self.clear_credentials() - - def __getstate__(self): - state_dict = copy.copy(self.__dict__) - # In case request is augmented by some foreign object such as - # credentials which handle auth - if "request" in state_dict: - del state_dict["request"] - if "connections" in state_dict: - del state_dict["connections"] - return state_dict - - def __setstate__(self, state): - self.__dict__.update(state) - self.connections = {} - - def _auth_from_challenge(self, host, request_uri, headers, response, content): - """A generator that creates Authorization objects - that can be applied to requests. - """ - challenges = auth._parse_www_authenticate(response, "www-authenticate") - for cred in self.credentials.iter(host): - for scheme in AUTH_SCHEME_ORDER: - if scheme in challenges: - yield AUTH_SCHEME_CLASSES[scheme](cred, host, request_uri, headers, response, content, self) - - def add_credentials(self, name, password, domain=""): - """Add a name and password that will be used - any time a request requires authentication.""" - self.credentials.add(name, password, domain) - - def add_certificate(self, key, cert, domain, password=None): - """Add a key and cert that will be used - any time a request requires authentication.""" - self.certificates.add(key, cert, domain, password) - - def clear_credentials(self): - """Remove all the names and passwords - that are used for authentication""" - self.credentials.clear() - self.authorizations = [] - - def _conn_request(self, conn, request_uri, method, body, headers): - i = 0 - seen_bad_status_line = False - while i < RETRIES: - i += 1 - try: - if hasattr(conn, "sock") and conn.sock is None: - conn.connect() - conn.request(method, request_uri, body, headers) - except socket.timeout: - raise - except socket.gaierror: - conn.close() - raise ServerNotFoundError("Unable to find the server at %s" % conn.host) - except ssl_SSLError: - conn.close() - raise - except socket.error as e: - err = 0 - if hasattr(e, "args"): - err = getattr(e, "args")[0] - else: - err = e.errno - if err == errno.ECONNREFUSED: # Connection refused - raise - if err in (errno.ENETUNREACH, errno.EADDRNOTAVAIL) and i < RETRIES: - continue # retry on potentially transient socket errors - except httplib.HTTPException: - # Just because the server closed the connection doesn't apparently mean - # that the server didn't send a response. - if hasattr(conn, "sock") and conn.sock is None: - if i < RETRIES - 1: - conn.close() - conn.connect() - continue - else: - conn.close() - raise - if i < RETRIES - 1: - conn.close() - conn.connect() - continue - try: - response = conn.getresponse() - except httplib.BadStatusLine: - # If we get a BadStatusLine on the first try then that means - # the connection just went stale, so retry regardless of the - # number of RETRIES set. - if not seen_bad_status_line and i == 1: - i = 0 - seen_bad_status_line = True - conn.close() - conn.connect() - continue - else: - conn.close() - raise - except (socket.error, httplib.HTTPException): - if i < RETRIES - 1: - conn.close() - conn.connect() - continue - else: - conn.close() - raise - else: - content = "" - if method == "HEAD": - conn.close() - else: - content = response.read() - response = Response(response) - if method != "HEAD": - content = _decompressContent(response, content) - break - return (response, content) - - def _request( - self, conn, host, absolute_uri, request_uri, method, body, headers, redirections, cachekey, - ): - """Do the actual request using the connection object - and also follow one level of redirects if necessary""" - - auths = [(auth.depth(request_uri), auth) for auth in self.authorizations if auth.inscope(host, request_uri)] - auth = auths and sorted(auths)[0][1] or None - if auth: - auth.request(method, request_uri, headers, body) - - (response, content) = self._conn_request(conn, request_uri, method, body, headers) - - if auth: - if auth.response(response, body): - auth.request(method, request_uri, headers, body) - (response, content) = self._conn_request(conn, request_uri, method, body, headers) - response._stale_digest = 1 - - if response.status == 401: - for authorization in self._auth_from_challenge(host, request_uri, headers, response, content): - authorization.request(method, request_uri, headers, body) - (response, content) = self._conn_request(conn, request_uri, method, body, headers) - if response.status != 401: - self.authorizations.append(authorization) - authorization.response(response, body) - break - - if self.follow_all_redirects or method in self.safe_methods or response.status in (303, 308): - if self.follow_redirects and response.status in self.redirect_codes: - # Pick out the location header and basically start from the beginning - # remembering first to strip the ETag header and decrement our 'depth' - if redirections: - if "location" not in response and response.status != 300: - raise RedirectMissingLocation( - _("Redirected but the response is missing a Location: header."), response, content, - ) - # Fix-up relative redirects (which violate an RFC 2616 MUST) - if "location" in response: - location = response["location"] - (scheme, authority, path, query, fragment) = parse_uri(location) - if authority == None: - response["location"] = urlparse.urljoin(absolute_uri, location) - if response.status == 308 or (response.status == 301 and method in self.safe_methods): - response["-x-permanent-redirect-url"] = response["location"] - if "content-location" not in response: - response["content-location"] = absolute_uri - _updateCache(headers, response, content, self.cache, cachekey) - if "if-none-match" in headers: - del headers["if-none-match"] - if "if-modified-since" in headers: - del headers["if-modified-since"] - if "authorization" in headers and not self.forward_authorization_headers: - del headers["authorization"] - if "location" in response: - location = response["location"] - old_response = copy.deepcopy(response) - if "content-location" not in old_response: - old_response["content-location"] = absolute_uri - redirect_method = method - if response.status in [302, 303]: - redirect_method = "GET" - body = None - (response, content) = self.request( - location, method=redirect_method, body=body, headers=headers, redirections=redirections - 1, - ) - response.previous = old_response - else: - raise RedirectLimit( - "Redirected more times than rediection_limit allows.", response, content, - ) - elif response.status in [200, 203] and method in self.safe_methods: - # Don't cache 206's since we aren't going to handle byte range requests - if "content-location" not in response: - response["content-location"] = absolute_uri - _updateCache(headers, response, content, self.cache, cachekey) - - return (response, content) - - def _normalize_headers(self, headers): - return _normalize_headers(headers) - - # Need to catch and rebrand some exceptions - # Then need to optionally turn all exceptions into status codes - # including all socket.* and httplib.* exceptions. - - def request( - self, uri, method="GET", body=None, headers=None, redirections=DEFAULT_MAX_REDIRECTS, connection_type=None, - ): - """ Performs a single HTTP request. - - The 'uri' is the URI of the HTTP resource and can begin with either - 'http' or 'https'. The value of 'uri' must be an absolute URI. - - The 'method' is the HTTP method to perform, such as GET, POST, DELETE, - etc. There is no restriction on the methods allowed. - - The 'body' is the entity body to be sent with the request. It is a - string object. - - Any extra headers that are to be sent with the request should be - provided in the 'headers' dictionary. - - The maximum number of redirect to follow before raising an - exception is 'redirections. The default is 5. - - The return value is a tuple of (response, content), the first - being and instance of the 'Response' class, the second being - a string that contains the response entity body. - """ - conn_key = "" - - try: - if headers is None: - headers = {} - else: - headers = self._normalize_headers(headers) - - if "user-agent" not in headers: - headers["user-agent"] = "Python-httplib2/%s (gzip)" % __version__ - - uri = iri2uri(uri) - # Prevent CWE-75 space injection to manipulate request via part of uri. - # Prevent CWE-93 CRLF injection to modify headers via part of uri. - uri = uri.replace(" ", "%20").replace("\r", "%0D").replace("\n", "%0A") - - (scheme, authority, request_uri, defrag_uri) = urlnorm(uri) - - proxy_info = self._get_proxy_info(scheme, authority) - - conn_key = scheme + ":" + authority - conn = self.connections.get(conn_key) - if conn is None: - if not connection_type: - connection_type = SCHEME_TO_CONNECTION[scheme] - certs = list(self.certificates.iter(authority)) - if scheme == "https": - if certs: - conn = self.connections[conn_key] = connection_type( - authority, - key_file=certs[0][0], - cert_file=certs[0][1], - timeout=self.timeout, - proxy_info=proxy_info, - ca_certs=self.ca_certs, - disable_ssl_certificate_validation=self.disable_ssl_certificate_validation, - ssl_version=self.ssl_version, - key_password=certs[0][2], - ) - else: - conn = self.connections[conn_key] = connection_type( - authority, - timeout=self.timeout, - proxy_info=proxy_info, - ca_certs=self.ca_certs, - disable_ssl_certificate_validation=self.disable_ssl_certificate_validation, - ssl_version=self.ssl_version, - ) - else: - conn = self.connections[conn_key] = connection_type( - authority, timeout=self.timeout, proxy_info=proxy_info - ) - conn.set_debuglevel(debuglevel) - - if "range" not in headers and "accept-encoding" not in headers: - headers["accept-encoding"] = "gzip, deflate" - - info = email.Message.Message() - cachekey = None - cached_value = None - if self.cache: - cachekey = defrag_uri.encode("utf-8") - cached_value = self.cache.get(cachekey) - if cached_value: - # info = email.message_from_string(cached_value) - # - # Need to replace the line above with the kludge below - # to fix the non-existent bug not fixed in this - # bug report: http://mail.python.org/pipermail/python-bugs-list/2005-September/030289.html - try: - info, content = cached_value.split("\r\n\r\n", 1) - feedparser = email.FeedParser.FeedParser() - feedparser.feed(info) - info = feedparser.close() - feedparser._parse = None - except (IndexError, ValueError): - self.cache.delete(cachekey) - cachekey = None - cached_value = None - - if ( - method in self.optimistic_concurrency_methods - and self.cache - and "etag" in info - and not self.ignore_etag - and "if-match" not in headers - ): - # http://www.w3.org/1999/04/Editing/ - headers["if-match"] = info["etag"] - - # https://tools.ietf.org/html/rfc7234 - # A cache MUST invalidate the effective Request URI as well as [...] Location and Content-Location - # when a non-error status code is received in response to an unsafe request method. - if self.cache and cachekey and method not in self.safe_methods: - self.cache.delete(cachekey) - - # Check the vary header in the cache to see if this request - # matches what varies in the cache. - if method in self.safe_methods and "vary" in info: - vary = info["vary"] - vary_headers = vary.lower().replace(" ", "").split(",") - for header in vary_headers: - key = "-varied-%s" % header - value = info[key] - if headers.get(header, None) != value: - cached_value = None - break - - if ( - self.cache - and cached_value - and (method in self.safe_methods or info["status"] == "308") - and "range" not in headers - ): - redirect_method = method - if info["status"] not in ("307", "308"): - redirect_method = "GET" - if "-x-permanent-redirect-url" in info: - # Should cached permanent redirects be counted in our redirection count? For now, yes. - if redirections <= 0: - raise RedirectLimit( - "Redirected more times than rediection_limit allows.", {}, "", - ) - (response, new_content) = self.request( - info["-x-permanent-redirect-url"], - method=redirect_method, - headers=headers, - redirections=redirections - 1, - ) - response.previous = Response(info) - response.previous.fromcache = True - else: - # Determine our course of action: - # Is the cached entry fresh or stale? - # Has the client requested a non-cached response? - # - # There seems to be three possible answers: - # 1. [FRESH] Return the cache entry w/o doing a GET - # 2. [STALE] Do the GET (but add in cache validators if available) - # 3. [TRANSPARENT] Do a GET w/o any cache validators (Cache-Control: no-cache) on the request - entry_disposition = _entry_disposition(info, headers) - - if entry_disposition == "FRESH": - if not cached_value: - info["status"] = "504" - content = "" - response = Response(info) - if cached_value: - response.fromcache = True - return (response, content) - - if entry_disposition == "STALE": - if "etag" in info and not self.ignore_etag and not "if-none-match" in headers: - headers["if-none-match"] = info["etag"] - if "last-modified" in info and not "last-modified" in headers: - headers["if-modified-since"] = info["last-modified"] - elif entry_disposition == "TRANSPARENT": - pass - - (response, new_content) = self._request( - conn, authority, uri, request_uri, method, body, headers, redirections, cachekey, - ) - - if response.status == 304 and method == "GET": - # Rewrite the cache entry with the new end-to-end headers - # Take all headers that are in response - # and overwrite their values in info. - # unless they are hop-by-hop, or are listed in the connection header. - - for key in _get_end2end_headers(response): - info[key] = response[key] - merged_response = Response(info) - if hasattr(response, "_stale_digest"): - merged_response._stale_digest = response._stale_digest - _updateCache(headers, merged_response, content, self.cache, cachekey) - response = merged_response - response.status = 200 - response.fromcache = True - - elif response.status == 200: - content = new_content - else: - self.cache.delete(cachekey) - content = new_content - else: - cc = _parse_cache_control(headers) - if "only-if-cached" in cc: - info["status"] = "504" - response = Response(info) - content = "" - else: - (response, content) = self._request( - conn, authority, uri, request_uri, method, body, headers, redirections, cachekey, - ) - except Exception as e: - is_timeout = isinstance(e, socket.timeout) - if is_timeout: - conn = self.connections.pop(conn_key, None) - if conn: - conn.close() - - if self.force_exception_to_status_code: - if isinstance(e, HttpLib2ErrorWithResponse): - response = e.response - content = e.content - response.status = 500 - response.reason = str(e) - elif is_timeout: - content = "Request Timeout" - response = Response({"content-type": "text/plain", "status": "408", "content-length": len(content),}) - response.reason = "Request Timeout" - else: - content = str(e) - response = Response({"content-type": "text/plain", "status": "400", "content-length": len(content),}) - response.reason = "Bad Request" - else: - raise - - return (response, content) - - def _get_proxy_info(self, scheme, authority): - """Return a ProxyInfo instance (or None) based on the scheme - and authority. - """ - hostname, port = urllib.splitport(authority) - proxy_info = self.proxy_info - if callable(proxy_info): - proxy_info = proxy_info(scheme) - - if hasattr(proxy_info, "applies_to") and not proxy_info.applies_to(hostname): - proxy_info = None - return proxy_info - - -class Response(dict): - """An object more like email.Message than httplib.HTTPResponse.""" - - """Is this response from our local cache""" - fromcache = False - """HTTP protocol version used by server. - - 10 for HTTP/1.0, 11 for HTTP/1.1. - """ - version = 11 - - "Status code returned by server. " - status = 200 - """Reason phrase returned by server.""" - reason = "Ok" - - previous = None - - def __init__(self, info): - # info is either an email.Message or - # an httplib.HTTPResponse object. - if isinstance(info, httplib.HTTPResponse): - for key, value in info.getheaders(): - self[key.lower()] = value - self.status = info.status - self["status"] = str(self.status) - self.reason = info.reason - self.version = info.version - elif isinstance(info, email.Message.Message): - for key, value in info.items(): - self[key.lower()] = value - self.status = int(self["status"]) - else: - for key, value in info.iteritems(): - self[key.lower()] = value - self.status = int(self.get("status", self.status)) - self.reason = self.get("reason", self.reason) - - def __getattr__(self, name): - if name == "dict": - return self - else: - raise AttributeError(name) diff --git a/shotgun_api3/lib/httplib2/python2/auth.py b/shotgun_api3/lib/httplib2/python2/auth.py deleted file mode 100644 index 7a1c2a7e8..000000000 --- a/shotgun_api3/lib/httplib2/python2/auth.py +++ /dev/null @@ -1,63 +0,0 @@ -import base64 -import re - -from ... import pyparsing as pp - -from .error import * - -UNQUOTE_PAIRS = re.compile(r"\\(.)") -unquote = lambda s, l, t: UNQUOTE_PAIRS.sub(r"\1", t[0][1:-1]) - -# https://tools.ietf.org/html/rfc7235#section-1.2 -# https://tools.ietf.org/html/rfc7235#appendix-B -tchar = "!#$%&'*+-.^_`|~" + pp.nums + pp.alphas -token = pp.Word(tchar).setName("token") -token68 = pp.Combine(pp.Word("-._~+/" + pp.nums + pp.alphas) + pp.Optional(pp.Word("=").leaveWhitespace())).setName( - "token68" -) - -quoted_string = pp.dblQuotedString.copy().setName("quoted-string").setParseAction(unquote) -auth_param_name = token.copy().setName("auth-param-name").addParseAction(pp.downcaseTokens) -auth_param = auth_param_name + pp.Suppress("=") + (quoted_string | token) -params = pp.Dict(pp.delimitedList(pp.Group(auth_param))) - -scheme = token("scheme") -challenge = scheme + (params("params") | token68("token")) - -authentication_info = params.copy() -www_authenticate = pp.delimitedList(pp.Group(challenge)) - - -def _parse_authentication_info(headers, headername="authentication-info"): - """https://tools.ietf.org/html/rfc7615 - """ - header = headers.get(headername, "").strip() - if not header: - return {} - try: - parsed = authentication_info.parseString(header) - except pp.ParseException as ex: - # print(ex.explain(ex)) - raise MalformedHeader(headername) - - return parsed.asDict() - - -def _parse_www_authenticate(headers, headername="www-authenticate"): - """Returns a dictionary of dictionaries, one dict per auth_scheme.""" - header = headers.get(headername, "").strip() - if not header: - return {} - try: - parsed = www_authenticate.parseString(header) - except pp.ParseException as ex: - # print(ex.explain(ex)) - raise MalformedHeader(headername) - - retval = { - challenge["scheme"].lower(): challenge["params"].asDict() - if "params" in challenge - else {"token": challenge.get("token")} - for challenge in parsed - } - return retval diff --git a/shotgun_api3/lib/httplib2/python2/cacerts.txt b/shotgun_api3/lib/httplib2/python2/cacerts.txt deleted file mode 100644 index 78a444c43..000000000 --- a/shotgun_api3/lib/httplib2/python2/cacerts.txt +++ /dev/null @@ -1,2225 +0,0 @@ -# Issuer: CN=GTE CyberTrust Global Root O=GTE Corporation OU=GTE CyberTrust Solutions, Inc. -# Subject: CN=GTE CyberTrust Global Root O=GTE Corporation OU=GTE CyberTrust Solutions, Inc. -# Label: "GTE CyberTrust Global Root" -# Serial: 421 -# MD5 Fingerprint: ca:3d:d3:68:f1:03:5c:d0:32:fa:b8:2b:59:e8:5a:db -# SHA1 Fingerprint: 97:81:79:50:d8:1c:96:70:cc:34:d8:09:cf:79:44:31:36:7e:f4:74 -# SHA256 Fingerprint: a5:31:25:18:8d:21:10:aa:96:4b:02:c7:b7:c6:da:32:03:17:08:94:e5:fb:71:ff:fb:66:67:d5:e6:81:0a:36 ------BEGIN CERTIFICATE----- -MIICWjCCAcMCAgGlMA0GCSqGSIb3DQEBBAUAMHUxCzAJBgNVBAYTAlVTMRgwFgYD -VQQKEw9HVEUgQ29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNv -bHV0aW9ucywgSW5jLjEjMCEGA1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJv -b3QwHhcNOTgwODEzMDAyOTAwWhcNMTgwODEzMjM1OTAwWjB1MQswCQYDVQQGEwJV -UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU -cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds -b2JhbCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVD6C28FCc6HrH -iM3dFw4usJTQGz0O9pTAipTHBsiQl8i4ZBp6fmw8U+E3KHNgf7KXUwefU/ltWJTS -r41tiGeA5u2ylc9yMcqlHHK6XALnZELn+aks1joNrI1CqiQBOeacPwGFVw1Yh0X4 -04Wqk2kmhXBIgD8SFcd5tB8FLztimQIDAQABMA0GCSqGSIb3DQEBBAUAA4GBAG3r -GwnpXtlR22ciYaQqPEh346B8pt5zohQDhT37qw4wxYMWM4ETCJ57NE7fQMh017l9 -3PR2VX2bY1QY6fDq81yx2YtCHrnAlU66+tXifPVoYb+O7AWXX1uw16OFNMQkpw0P -lZPvy5TYnh+dXIVtx6quTx8itc2VrbqnzPmrC3p/ ------END CERTIFICATE----- - -# Issuer: CN=Thawte Server CA O=Thawte Consulting cc OU=Certification Services Division -# Subject: CN=Thawte Server CA O=Thawte Consulting cc OU=Certification Services Division -# Label: "Thawte Server CA" -# Serial: 1 -# MD5 Fingerprint: c5:70:c4:a2:ed:53:78:0c:c8:10:53:81:64:cb:d0:1d -# SHA1 Fingerprint: 23:e5:94:94:51:95:f2:41:48:03:b4:d5:64:d2:a3:a3:f5:d8:8b:8c -# SHA256 Fingerprint: b4:41:0b:73:e2:e6:ea:ca:47:fb:c4:2f:8f:a4:01:8a:f4:38:1d:c5:4c:fa:a8:44:50:46:1e:ed:09:45:4d:e9 ------BEGIN CERTIFICATE----- -MIIDEzCCAnygAwIBAgIBATANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMCWkEx -FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD -VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv -biBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEm -MCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wHhcNOTYwODAx -MDAwMDAwWhcNMjAxMjMxMjM1OTU5WjCBxDELMAkGA1UEBhMCWkExFTATBgNVBAgT -DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3 -dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNl -cyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3 -DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQAD -gY0AMIGJAoGBANOkUG7I/1Zr5s9dtuoMaHVHoqrC2oQl/Kj0R1HahbUgdJSGHg91 -yekIYfUGbTBuFRkC6VLAYttNmZ7iagxEOM3+vuNkCXDF/rFrKbYvScg71CcEJRCX -L+eQbcAoQpnXTEPew/UhbVSfXcNY4cDk2VuwuNy0e982OsK1ZiIS1ocNAgMBAAGj -EzARMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAB/pMaVz7lcxG -7oWDTSEwjsrZqG9JGubaUeNgcGyEYRGhGshIPllDfU+VPaGLtwtimHp1it2ITk6e -QNuozDJ0uW8NxuOzRAvZim+aKZuZGCg70eNAKJpaPNW15yAbi8qkq43pUdniTCxZ -qdq5snUb9kLy78fyGPmJvKP/iiMucEc= ------END CERTIFICATE----- - -# Issuer: CN=Thawte Premium Server CA O=Thawte Consulting cc OU=Certification Services Division -# Subject: CN=Thawte Premium Server CA O=Thawte Consulting cc OU=Certification Services Division -# Label: "Thawte Premium Server CA" -# Serial: 1 -# MD5 Fingerprint: 06:9f:69:79:16:66:90:02:1b:8c:8c:a2:c3:07:6f:3a -# SHA1 Fingerprint: 62:7f:8d:78:27:65:63:99:d2:7d:7f:90:44:c9:fe:b3:f3:3e:fa:9a -# SHA256 Fingerprint: ab:70:36:36:5c:71:54:aa:29:c2:c2:9f:5d:41:91:16:3b:16:2a:22:25:01:13:57:d5:6d:07:ff:a7:bc:1f:72 ------BEGIN CERTIFICATE----- -MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMCWkEx -FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD -VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv -biBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhhd3RlIFByZW1pdW0gU2Vy -dmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNlcnZlckB0aGF3dGUuY29t -MB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgc4xCzAJBgNVBAYTAlpB -MRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEdMBsG -A1UEChMUVGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNVBAsTH0NlcnRpZmljYXRp -b24gU2VydmljZXMgRGl2aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNl -cnZlciBDQTEoMCYGCSqGSIb3DQEJARYZcHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNv -bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2aovXwlue2oFBYo847kkE -VdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560ZXUCTe/LCaIhUdib0GfQ -ug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j+ao6hnO2RlNYyIkFvYMR -uHM/qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG -9w0BAQQFAAOBgQAmSCwWwlj66BZ0DKqqX1Q/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUI -hfzJATj/Tb7yFkJD57taRvvBxhEf8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZa4JM -pAwSremkrj/xw0llmozFyD4lt5SZu5IycQfwhl7tUCemDaYj+bvLpgcUQg== ------END CERTIFICATE----- - -# Issuer: O=Equifax OU=Equifax Secure Certificate Authority -# Subject: O=Equifax OU=Equifax Secure Certificate Authority -# Label: "Equifax Secure CA" -# Serial: 903804111 -# MD5 Fingerprint: 67:cb:9d:c0:13:24:8a:82:9b:b2:17:1e:d1:1b:ec:d4 -# SHA1 Fingerprint: d2:32:09:ad:23:d3:14:23:21:74:e4:0d:7f:9d:62:13:97:86:63:3a -# SHA256 Fingerprint: 08:29:7a:40:47:db:a2:36:80:c7:31:db:6e:31:76:53:ca:78:48:e1:be:bd:3a:0b:01:79:a7:07:f9:2c:f1:78 ------BEGIN CERTIFICATE----- -MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV -UzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2Vy -dGlmaWNhdGUgQXV0aG9yaXR5MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1 -MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoTB0VxdWlmYXgxLTArBgNVBAsTJEVx -dWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCBnzANBgkqhkiG9w0B -AQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPRfM6f -BeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+A -cJkVV5MW8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kC -AwEAAaOCAQkwggEFMHAGA1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQ -MA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlm -aWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTgw -ODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvSspXXR9gj -IBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQF -MAMBAf8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUA -A4GBAFjOKer89961zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y -7qj/WsjTVbJmcVfewCHrPSqnI0kBBIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh -1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee9570+sB3c4 ------END CERTIFICATE----- - -# Issuer: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority - G2/(c) 1998 VeriSign, Inc. - For authorized use only/VeriSign Trust Network -# Subject: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority - G2/(c) 1998 VeriSign, Inc. - For authorized use only/VeriSign Trust Network -# Label: "Verisign Class 3 Public Primary Certification Authority - G2" -# Serial: 167285380242319648451154478808036881606 -# MD5 Fingerprint: a2:33:9b:4c:74:78:73:d4:6c:e7:c1:f3:8d:cb:5c:e9 -# SHA1 Fingerprint: 85:37:1c:a6:e5:50:14:3d:ce:28:03:47:1b:de:3a:09:e8:f8:77:0f -# SHA256 Fingerprint: 83:ce:3c:12:29:68:8a:59:3d:48:5f:81:97:3c:0f:91:95:43:1e:da:37:cc:5e:36:43:0e:79:c7:a8:88:63:8b ------BEGIN CERTIFICATE----- -MIIDAjCCAmsCEH3Z/gfPqB63EHln+6eJNMYwDQYJKoZIhvcNAQEFBQAwgcExCzAJ -BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xh -c3MgMyBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcy -MTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3Jp -emVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMB4X -DTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVTMRcw -FQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMg -UHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEo -YykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5 -MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEB -AQUAA4GNADCBiQKBgQDMXtERXVxp0KvTuWpMmR9ZmDCOFoUgRm1HP9SFIIThbbP4 -pO0M8RcPO/mn+SXXwc+EY/J8Y8+iR/LGWzOOZEAEaMGAuWQcRXfH2G71lSk8UOg0 -13gfqLptQ5GVj0VXXn7F+8qkBOvqlzdUMG+7AUcyM83cV5tkaWH4mx0ciU9cZwID -AQABMA0GCSqGSIb3DQEBBQUAA4GBAFFNzb5cy5gZnBWyATl4Lk0PZ3BwmcYQWpSk -U01UbSuvDV1Ai2TT1+7eVmGSX6bEHRBhNtMsJzzoKQm5EWR0zLVznxxIqbxhAe7i -F6YM40AIOw7n60RzKprxaZLvcRTDOaxxp5EJb+RxBrO6WVcmeQD2+A2iMzAo1KpY -oJ2daZH9 ------END CERTIFICATE----- - -# Issuer: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA -# Subject: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA -# Label: "GlobalSign Root CA" -# Serial: 4835703278459707669005204 -# MD5 Fingerprint: 3e:45:52:15:09:51:92:e1:b7:5d:37:9f:b1:87:29:8a -# SHA1 Fingerprint: b1:bc:96:8b:d4:f4:9d:62:2a:a8:9a:81:f2:15:01:52:a4:1d:82:9c -# SHA256 Fingerprint: eb:d4:10:40:e4:bb:3e:c7:42:c9:e3:81:d3:1e:f2:a4:1a:48:b6:68:5c:96:e7:ce:f3:c1:df:6c:d4:33:1c:99 ------BEGIN CERTIFICATE----- -MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG -A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv -b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw -MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i -YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT -aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ -jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp -xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp -1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG -snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ -U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8 -9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E -BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B -AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz -yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE -38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP -AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad -DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME -HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== ------END CERTIFICATE----- - -# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R2 -# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R2 -# Label: "GlobalSign Root CA - R2" -# Serial: 4835703278459682885658125 -# MD5 Fingerprint: 94:14:77:7e:3e:5e:fd:8f:30:bd:41:b0:cf:e7:d0:30 -# SHA1 Fingerprint: 75:e0:ab:b6:13:85:12:27:1c:04:f8:5f:dd:de:38:e4:b7:24:2e:fe -# SHA256 Fingerprint: ca:42:dd:41:74:5f:d0:b8:1e:b9:02:36:2c:f9:d8:bf:71:9d:a1:bd:1b:1e:fc:94:6f:5b:4c:99:f4:2c:1b:9e ------BEGIN CERTIFICATE----- -MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4G -A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNp -Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1 -MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEG -A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI -hvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6ErPL -v4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8 -eoLrvozps6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklq -tTleiDTsvHgMCJiEbKjNS7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzd -C9XZzPnqJworc5HGnRusyMvo4KD0L5CLTfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pa -zq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6CygPCm48CAwEAAaOBnDCB -mTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUm+IH -V2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5n -bG9iYWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG -3lm0mi3f3BmGLjANBgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4Gs -J0/WwbgcQ3izDJr86iw8bmEbTUsp9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO -291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu01yiPqFbQfXf5WRDLenVOavS -ot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG79G+dwfCMNYxd -AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7 -TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg== ------END CERTIFICATE----- - -# Issuer: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 1 Policy Validation Authority -# Subject: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 1 Policy Validation Authority -# Label: "ValiCert Class 1 VA" -# Serial: 1 -# MD5 Fingerprint: 65:58:ab:15:ad:57:6c:1e:a8:a7:b5:69:ac:bf:ff:eb -# SHA1 Fingerprint: e5:df:74:3c:b6:01:c4:9b:98:43:dc:ab:8c:e8:6a:81:10:9f:e4:8e -# SHA256 Fingerprint: f4:c1:49:55:1a:30:13:a3:5b:c7:bf:fe:17:a7:f3:44:9b:c1:ab:5b:5a:0a:e7:4b:06:c2:3b:90:00:4c:01:04 ------BEGIN CERTIFICATE----- -MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0 -IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz -BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y -aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG -9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNTIyMjM0OFoXDTE5MDYy -NTIyMjM0OFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y -azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs -YXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw -Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl -cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYWYJ6ibiWuqYvaG9Y -LqdUHAZu9OqNSLwxlBfw8068srg1knaw0KWlAdcAAxIiGQj4/xEjm84H9b9pGib+ -TunRf50sQB1ZaG6m+FiwnRqP0z/x3BkGgagO4DrdyFNFCQbmD3DD+kCmDuJWBQ8Y -TfwggtFzVXSNdnKgHZ0dwN0/cQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFBoPUn0 -LBwGlN+VYH+Wexf+T3GtZMjdd9LvWVXoP+iOBSoh8gfStadS/pyxtuJbdxdA6nLW -I8sogTLDAHkY7FkXicnGah5xyf23dKUlRWnFSKsZ4UWKJWsZ7uW7EvV/96aNUcPw -nXS3qT6gpf+2SQMT2iLM7XGCK5nPOrf1LXLI ------END CERTIFICATE----- - -# Issuer: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 2 Policy Validation Authority -# Subject: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 2 Policy Validation Authority -# Label: "ValiCert Class 2 VA" -# Serial: 1 -# MD5 Fingerprint: a9:23:75:9b:ba:49:36:6e:31:c2:db:f2:e7:66:ba:87 -# SHA1 Fingerprint: 31:7a:2a:d0:7f:2b:33:5e:f5:a1:c3:4e:4b:57:e8:b7:d8:f1:fc:a6 -# SHA256 Fingerprint: 58:d0:17:27:9c:d4:dc:63:ab:dd:b1:96:a6:c9:90:6c:30:c4:e0:87:83:ea:e8:c1:60:99:54:d6:93:55:59:6b ------BEGIN CERTIFICATE----- -MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0 -IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz -BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y -aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG -9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMTk1NFoXDTE5MDYy -NjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y -azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs -YXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw -Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl -cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOOnHK5avIWZJV16vY -dA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVCCSRrCl6zfN1SLUzm1NZ9 -WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7RfZHM047QS -v4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9v -UJSZSWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTu -IYEZoDJJKPTEjlbVUjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwC -W/POuZ6lcg5Ktz885hZo+L7tdEy8W9ViH0Pd ------END CERTIFICATE----- - -# Issuer: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 3 Policy Validation Authority -# Subject: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 3 Policy Validation Authority -# Label: "RSA Root Certificate 1" -# Serial: 1 -# MD5 Fingerprint: a2:6f:53:b7:ee:40:db:4a:68:e7:fa:18:d9:10:4b:72 -# SHA1 Fingerprint: 69:bd:8c:f4:9c:d3:00:fb:59:2e:17:93:ca:55:6a:f3:ec:aa:35:fb -# SHA256 Fingerprint: bc:23:f9:8a:31:3c:b9:2d:e3:bb:fc:3a:5a:9f:44:61:ac:39:49:4c:4a:e1:5a:9e:9d:f1:31:e9:9b:73:01:9a ------BEGIN CERTIFICATE----- -MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0 -IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz -BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y -aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG -9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMjIzM1oXDTE5MDYy -NjAwMjIzM1owgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y -azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs -YXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw -Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl -cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDjmFGWHOjVsQaBalfD -cnWTq8+epvzzFlLWLU2fNUSoLgRNB0mKOCn1dzfnt6td3zZxFJmP3MKS8edgkpfs -2Ejcv8ECIMYkpChMMFp2bbFc893enhBxoYjHW5tBbcqwuI4V7q0zK89HBFx1cQqY -JJgpp0lZpd34t0NiYfPT4tBVPwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFa7AliE -Zwgs3x/be0kz9dNnnfS0ChCzycUs4pJqcXgn8nCDQtM+z6lU9PHYkhaM0QTLS6vJ -n0WuPIqpsHEzXcjFV9+vqDWzf4mH6eglkrh/hXqu1rweN1gqZ8mRzyqBPu3GOd/A -PhmcGcwTTYJBtYze4D1gCCAPRX5ron+jjBXu ------END CERTIFICATE----- - -# Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only -# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only -# Label: "Verisign Class 3 Public Primary Certification Authority - G3" -# Serial: 206684696279472310254277870180966723415 -# MD5 Fingerprint: cd:68:b6:a7:c7:c4:ce:75:e0:1d:4f:57:44:61:92:09 -# SHA1 Fingerprint: 13:2d:0d:45:53:4b:69:97:cd:b2:d5:c3:39:e2:55:76:60:9b:5c:c6 -# SHA256 Fingerprint: eb:04:cf:5e:b1:f3:9a:fa:76:2f:2b:b1:20:f2:96:cb:a5:20:c1:b9:7d:b1:58:95:65:b8:1c:b9:a1:7b:72:44 ------BEGIN CERTIFICATE----- -MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQsw -CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl -cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu -LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT -aWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp -dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD -VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT -aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ -bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu -IENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg -LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMu6nFL8eB8aHm8b -N3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1EUGO+i2t -KmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGu -kxUccLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBm -CC+Vk7+qRy+oRpfwEuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJ -Xwzw3sJ2zq/3avL6QaaiMxTJ5Xpj055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWu -imi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAERSWwauSCPc/L8my/uRan2Te -2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5fj267Cz3qWhMe -DGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC -/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565p -F4ErWjfJXir0xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGt -TxzhT5yvDwyd93gN2PQ1VoDat20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ== ------END CERTIFICATE----- - -# Issuer: CN=VeriSign Class 4 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only -# Subject: CN=VeriSign Class 4 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only -# Label: "Verisign Class 4 Public Primary Certification Authority - G3" -# Serial: 314531972711909413743075096039378935511 -# MD5 Fingerprint: db:c8:f2:27:2e:b1:ea:6a:29:23:5d:fe:56:3e:33:df -# SHA1 Fingerprint: c8:ec:8c:87:92:69:cb:4b:ab:39:e9:8d:7e:57:67:f3:14:95:73:9d -# SHA256 Fingerprint: e3:89:36:0d:0f:db:ae:b3:d2:50:58:4b:47:30:31:4e:22:2f:39:c1:56:a0:20:14:4e:8d:96:05:61:79:15:06 ------BEGIN CERTIFICATE----- -MIIEGjCCAwICEQDsoKeLbnVqAc/EfMwvlF7XMA0GCSqGSIb3DQEBBQUAMIHKMQsw -CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl -cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu -LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT -aWduIENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp -dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD -VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT -aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ -bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu -IENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg -LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK3LpRFpxlmr8Y+1 -GQ9Wzsy1HyDkniYlS+BzZYlZ3tCD5PUPtbut8XzoIfzk6AzufEUiGXaStBO3IFsJ -+mGuqPKljYXCKtbeZjbSmwL0qJJgfJxptI8kHtCGUvYynEFYHiK9zUVilQhu0Gbd -U6LM8BDcVHOLBKFGMzNcF0C5nk3T875Vg+ixiY5afJqWIpA7iCXy0lOIAgwLePLm -NxdLMEYH5IBtptiWLugs+BGzOA1mppvqySNb247i8xOOGlktqgLw7KSHZtzBP/XY -ufTsgsbSPZUd5cBPhMnZo0QoBmrXRazwa2rvTl/4EYIeOGM0ZlDUPpNz+jDDZq3/ -ky2X7wMCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAj/ola09b5KROJ1WrIhVZPMq1 -CtRK26vdoV9TxaBXOcLORyu+OshWv8LZJxA6sQU8wHcxuzrTBXttmhwwjIDLk5Mq -g6sFUYICABFna/OIYUdfA5PVWw3g8dShMjWFsjrbsIKr0csKvE+MW8VLADsfKoKm -fjaF3H48ZwC15DtS4KjrXRX5xm3wrR0OhbepmnMUWluPQSjA1egtTaRezarZ7c7c -2NU8Qh0XwRJdRTjDOPP8hS6DRkiy1yBfkjaP53kPmF6Z6PDQpLv1U70qzlmwr25/ -bLvSHgCwIe34QWKCudiyxLtGUPMxxY8BqHTr9Xgn2uf3ZkPznoM+IKrDNWCRzg== ------END CERTIFICATE----- - -# Issuer: CN=Entrust.net Secure Server Certification Authority O=Entrust.net OU=www.entrust.net/CPS incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited -# Subject: CN=Entrust.net Secure Server Certification Authority O=Entrust.net OU=www.entrust.net/CPS incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited -# Label: "Entrust.net Secure Server CA" -# Serial: 927650371 -# MD5 Fingerprint: df:f2:80:73:cc:f1:e6:61:73:fc:f5:42:e9:c5:7c:ee -# SHA1 Fingerprint: 99:a6:9b:e6:1a:fe:88:6b:4d:2b:82:00:7c:b8:54:fc:31:7e:15:39 -# SHA256 Fingerprint: 62:f2:40:27:8c:56:4c:4d:d8:bf:7d:9d:4f:6f:36:6e:a8:94:d2:2f:5f:34:d9:89:a9:83:ac:ec:2f:ff:ed:50 ------BEGIN CERTIFICATE----- -MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC -VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u -ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc -KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u -ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw05OTA1 -MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIGA1UE -ChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5j -b3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF -bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUg -U2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUA -A4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQaO2f55M28Qpku0f1BBc/ -I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5gXpa0zf3 -wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OC -AdcwggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHb -oIHYpIHVMIHSMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5 -BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1p -dHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1pdGVk -MTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRp -b24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu -dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0 -MFqBDzIwMTkwNTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8Bdi -E1U9s/8KAGv7UISX8+1i0BowHQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAa -MAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EABAwwChsEVjQuMAMCBJAwDQYJKoZI -hvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyNEwr75Ji174z4xRAN -95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9n9cd -2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI= ------END CERTIFICATE----- - -# Issuer: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited -# Subject: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited -# Label: "Entrust.net Premium 2048 Secure Server CA" -# Serial: 946059622 -# MD5 Fingerprint: ba:21:ea:20:d6:dd:db:8f:c1:57:8b:40:ad:a1:fc:fc -# SHA1 Fingerprint: 80:1d:62:d0:7b:44:9d:5c:5c:03:5c:98:ea:61:fa:44:3c:2a:58:fe -# SHA256 Fingerprint: d1:c3:39:ea:27:84:eb:87:0f:93:4f:c5:63:4e:4a:a9:ad:55:05:01:64:01:f2:64:65:d3:7a:57:46:63:35:9f ------BEGIN CERTIFICATE----- -MIIEXDCCA0SgAwIBAgIEOGO5ZjANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML -RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp -bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5 -IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRp -ZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQxNzUwNTFaFw0xOTEy -MjQxODIwNTFaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3 -LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxp -YWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEG -A1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArU1LqRKGsuqjIAcVFmQq -K0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOLGp18EzoOH1u3Hs/lJBQe -sYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSrhRSGlVuX -MlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVT -XTzWnLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/ -HoZdenoVve8AjhUiVBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH -4QIDAQABo3QwcjARBglghkgBhvhCAQEEBAMCAAcwHwYDVR0jBBgwFoAUVeSB0RGA -vtiJuQijMfmhJAkWuXAwHQYDVR0OBBYEFFXkgdERgL7YibkIozH5oSQJFrlwMB0G -CSqGSIb2fQdBAAQQMA4bCFY1LjA6NC4wAwIEkDANBgkqhkiG9w0BAQUFAAOCAQEA -WUesIYSKF8mciVMeuoCFGsY8Tj6xnLZ8xpJdGGQC49MGCBFhfGPjK50xA3B20qMo -oPS7mmNz7W3lKtvtFKkrxjYR0CvrB4ul2p5cGZ1WEvVUKcgF7bISKo30Axv/55IQ -h7A6tcOdBTcSo8f0FbnVpDkWm1M6I5HxqIKiaohowXkCIryqptau37AUX7iH0N18 -f3v/rxzP5tsHrV7bhZ3QKw0z2wTR5klAEyt2+z7pnIkPFc4YsIV4IU9rTw76NmfN -B/L/CNDi3tm/Kq+4h4YhPATKt5Rof8886ZjXOP/swNlQ8C5LWK5Gb9Auw2DaclVy -vUxFnmG6v4SBkgPR0ml8xQ== ------END CERTIFICATE----- - -# Issuer: CN=Baltimore CyberTrust Root O=Baltimore OU=CyberTrust -# Subject: CN=Baltimore CyberTrust Root O=Baltimore OU=CyberTrust -# Label: "Baltimore CyberTrust Root" -# Serial: 33554617 -# MD5 Fingerprint: ac:b6:94:a5:9c:17:e0:d7:91:52:9b:b1:97:06:a6:e4 -# SHA1 Fingerprint: d4:de:20:d0:5e:66:fc:53:fe:1a:50:88:2c:78:db:28:52:ca:e4:74 -# SHA256 Fingerprint: 16:af:57:a9:f6:76:b0:ab:12:60:95:aa:5e:ba:de:f2:2a:b3:11:19:d6:44:ac:95:cd:4b:93:db:f3:f2:6a:eb ------BEGIN CERTIFICATE----- -MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ -RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD -VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX -DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y -ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy -VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr -mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr -IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK -mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu -XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy -dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye -jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1 -BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3 -DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92 -9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx -jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0 -Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz -ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS -R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp ------END CERTIFICATE----- - -# Issuer: CN=Equifax Secure Global eBusiness CA-1 O=Equifax Secure Inc. -# Subject: CN=Equifax Secure Global eBusiness CA-1 O=Equifax Secure Inc. -# Label: "Equifax Secure Global eBusiness CA" -# Serial: 1 -# MD5 Fingerprint: 8f:5d:77:06:27:c4:98:3c:5b:93:78:e7:d7:7d:9b:cc -# SHA1 Fingerprint: 7e:78:4a:10:1c:82:65:cc:2d:e1:f1:6d:47:b4:40:ca:d9:0a:19:45 -# SHA256 Fingerprint: 5f:0b:62:ea:b5:e3:53:ea:65:21:65:16:58:fb:b6:53:59:f4:43:28:0a:4a:fb:d1:04:d7:7d:10:f9:f0:4c:07 ------BEGIN CERTIFICATE----- -MIICkDCCAfmgAwIBAgIBATANBgkqhkiG9w0BAQQFADBaMQswCQYDVQQGEwJVUzEc -MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEtMCsGA1UEAxMkRXF1aWZheCBT -ZWN1cmUgR2xvYmFsIGVCdXNpbmVzcyBDQS0xMB4XDTk5MDYyMTA0MDAwMFoXDTIw -MDYyMTA0MDAwMFowWjELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0VxdWlmYXggU2Vj -dXJlIEluYy4xLTArBgNVBAMTJEVxdWlmYXggU2VjdXJlIEdsb2JhbCBlQnVzaW5l -c3MgQ0EtMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAuucXkAJlsTRVPEnC -UdXfp9E3j9HngXNBUmCbnaEXJnitx7HoJpQytd4zjTov2/KaelpzmKNc6fuKcxtc -58O/gGzNqfTWK8D3+ZmqY6KxRwIP1ORROhI8bIpaVIRw28HFkM9yRcuoWcDNM50/ -o5brhTMhHD4ePmBudpxnhcXIw2ECAwEAAaNmMGQwEQYJYIZIAYb4QgEBBAQDAgAH -MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUvqigdHJQa0S3ySPY+6j/s1dr -aGwwHQYDVR0OBBYEFL6ooHRyUGtEt8kj2Puo/7NXa2hsMA0GCSqGSIb3DQEBBAUA -A4GBADDiAVGqx+pf2rnQZQ8w1j7aDRRJbpGTJxQx78T3LUX47Me/okENI7SS+RkA -Z70Br83gcfxaz2TE4JaY0KNA4gGK7ycH8WUBikQtBmV1UsCGECAhX2xrD2yuCRyv -8qIYNMR1pHMc8Y3c7635s3a0kr/clRAevsvIO1qEYBlWlKlV ------END CERTIFICATE----- - -# Issuer: CN=Equifax Secure eBusiness CA-1 O=Equifax Secure Inc. -# Subject: CN=Equifax Secure eBusiness CA-1 O=Equifax Secure Inc. -# Label: "Equifax Secure eBusiness CA 1" -# Serial: 4 -# MD5 Fingerprint: 64:9c:ef:2e:44:fc:c6:8f:52:07:d0:51:73:8f:cb:3d -# SHA1 Fingerprint: da:40:18:8b:91:89:a3:ed:ee:ae:da:97:fe:2f:9d:f5:b7:d1:8a:41 -# SHA256 Fingerprint: cf:56:ff:46:a4:a1:86:10:9d:d9:65:84:b5:ee:b5:8a:51:0c:42:75:b0:e5:f9:4f:40:bb:ae:86:5e:19:f6:73 ------BEGIN CERTIFICATE----- -MIICgjCCAeugAwIBAgIBBDANBgkqhkiG9w0BAQQFADBTMQswCQYDVQQGEwJVUzEc -MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBT -ZWN1cmUgZUJ1c2luZXNzIENBLTEwHhcNOTkwNjIxMDQwMDAwWhcNMjAwNjIxMDQw -MDAwWjBTMQswCQYDVQQGEwJVUzEcMBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5j -LjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNzIENBLTEwgZ8wDQYJ -KoZIhvcNAQEBBQADgY0AMIGJAoGBAM4vGbwXt3fek6lfWg0XTzQaDJj0ItlZ1MRo -RvC0NcWFAyDGr0WlIVFFQesWWDYyb+JQYmT5/VGcqiTZ9J2DKocKIdMSODRsjQBu -WqDZQu4aIZX5UkxVWsUPOE9G+m34LjXWHXzr4vCwdYDIqROsvojvOm6rXyo4YgKw -Env+j6YDAgMBAAGjZjBkMBEGCWCGSAGG+EIBAQQEAwIABzAPBgNVHRMBAf8EBTAD -AQH/MB8GA1UdIwQYMBaAFEp4MlIR21kWNl7fwRQ2QGpHfEyhMB0GA1UdDgQWBBRK -eDJSEdtZFjZe38EUNkBqR3xMoTANBgkqhkiG9w0BAQQFAAOBgQB1W6ibAxHm6VZM -zfmpTMANmvPMZWnmJXbMWbfWVMMdzZmsGd20hdXgPfxiIKeES1hl8eL5lSE/9dR+ -WB5Hh1Q+WKG1tfgq73HnvMP2sUlG4tega+VWeponmHxGYhTnyfxuAxJ5gDgdSIKN -/Bf+KpYrtWKmpj29f5JZzVoqgrI3eQ== ------END CERTIFICATE----- - -# Issuer: O=Equifax Secure OU=Equifax Secure eBusiness CA-2 -# Subject: O=Equifax Secure OU=Equifax Secure eBusiness CA-2 -# Label: "Equifax Secure eBusiness CA 2" -# Serial: 930140085 -# MD5 Fingerprint: aa:bf:bf:64:97:da:98:1d:6f:c6:08:3a:95:70:33:ca -# SHA1 Fingerprint: 39:4f:f6:85:0b:06:be:52:e5:18:56:cc:10:e1:80:e8:82:b3:85:cc -# SHA256 Fingerprint: 2f:27:4e:48:ab:a4:ac:7b:76:59:33:10:17:75:50:6d:c3:0e:e3:8e:f6:ac:d5:c0:49:32:cf:e0:41:23:42:20 ------BEGIN CERTIFICATE----- -MIIDIDCCAomgAwIBAgIEN3DPtTANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV -UzEXMBUGA1UEChMORXF1aWZheCBTZWN1cmUxJjAkBgNVBAsTHUVxdWlmYXggU2Vj -dXJlIGVCdXNpbmVzcyBDQS0yMB4XDTk5MDYyMzEyMTQ0NVoXDTE5MDYyMzEyMTQ0 -NVowTjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkVxdWlmYXggU2VjdXJlMSYwJAYD -VQQLEx1FcXVpZmF4IFNlY3VyZSBlQnVzaW5lc3MgQ0EtMjCBnzANBgkqhkiG9w0B -AQEFAAOBjQAwgYkCgYEA5Dk5kx5SBhsoNviyoynF7Y6yEb3+6+e0dMKP/wXn2Z0G -vxLIPw7y1tEkshHe0XMJitSxLJgJDR5QRrKDpkWNYmi7hRsgcDKqQM2mll/EcTc/ -BPO3QSQ5BxoeLmFYoBIL5aXfxavqN3HMHMg3OrmXUqesxWoklE6ce8/AatbfIb0C -AwEAAaOCAQkwggEFMHAGA1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEX -MBUGA1UEChMORXF1aWZheCBTZWN1cmUxJjAkBgNVBAsTHUVxdWlmYXggU2VjdXJl -IGVCdXNpbmVzcyBDQS0yMQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTkw -NjIzMTIxNDQ1WjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUUJ4L6q9euSBIplBq -y/3YIHqngnYwHQYDVR0OBBYEFFCeC+qvXrkgSKZQasv92CB6p4J2MAwGA1UdEwQF -MAMBAf8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUA -A4GBAAyGgq3oThr1jokn4jVYPSm0B482UJW/bsGe68SQsoWou7dC4A8HOd/7npCy -0cE+U58DRLB+S/Rv5Hwf5+Kx5Lia78O9zt4LMjTZ3ijtM2vE1Nc9ElirfQkty3D1 -E4qUoSek1nDFbZS1yX2doNLGCEnZZpum0/QL3MUmV+GRMOrN ------END CERTIFICATE----- - -# Issuer: CN=AddTrust Class 1 CA Root O=AddTrust AB OU=AddTrust TTP Network -# Subject: CN=AddTrust Class 1 CA Root O=AddTrust AB OU=AddTrust TTP Network -# Label: "AddTrust Low-Value Services Root" -# Serial: 1 -# MD5 Fingerprint: 1e:42:95:02:33:92:6b:b9:5f:c0:7f:da:d6:b2:4b:fc -# SHA1 Fingerprint: cc:ab:0e:a0:4c:23:01:d6:69:7b:dd:37:9f:cd:12:eb:24:e3:94:9d -# SHA256 Fingerprint: 8c:72:09:27:9a:c0:4e:27:5e:16:d0:7f:d3:b7:75:e8:01:54:b5:96:80:46:e3:1f:52:dd:25:76:63:24:e9:a7 ------BEGIN CERTIFICATE----- -MIIEGDCCAwCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQGEwJTRTEU -MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3 -b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwHhcNMDAwNTMw -MTAzODMxWhcNMjAwNTMwMTAzODMxWjBlMQswCQYDVQQGEwJTRTEUMBIGA1UEChML -QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYD -VQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUA -A4IBDwAwggEKAoIBAQCWltQhSWDia+hBBwzexODcEyPNwTXH+9ZOEQpnXvUGW2ul -CDtbKRY654eyNAbFvAWlA3yCyykQruGIgb3WntP+LVbBFc7jJp0VLhD7Bo8wBN6n -tGO0/7Gcrjyvd7ZWxbWroulpOj0OM3kyP3CCkplhbY0wCI9xP6ZIVxn4JdxLZlyl -dI+Yrsj5wAYi56xz36Uu+1LcsRVlIPo1Zmne3yzxbrww2ywkEtvrNTVokMsAsJch -PXQhI2U0K7t4WaPW4XY5mqRJjox0r26kmqPZm9I4XJuiGMx1I4S+6+JNM3GOGvDC -+Mcdoq0Dlyz4zyXG9rgkMbFjXZJ/Y/AlyVMuH79NAgMBAAGjgdIwgc8wHQYDVR0O -BBYEFJWxtPCUtr3H2tERCSG+wa9J/RB7MAsGA1UdDwQEAwIBBjAPBgNVHRMBAf8E -BTADAQH/MIGPBgNVHSMEgYcwgYSAFJWxtPCUtr3H2tERCSG+wa9J/RB7oWmkZzBl -MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFk -ZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENB -IFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBACxtZBsfzQ3duQH6lmM0MkhHma6X -7f1yFqZzR1r0693p9db7RcwpiURdv0Y5PejuvE1Uhh4dbOMXJ0PhiVYrqW9yTkkz -43J8KiOavD7/KCrto/8cI7pDVwlnTUtiBi34/2ydYB7YHEt9tTEv2dB8Xfjea4MY -eDdXL+gzB2ffHsdrKpV2ro9Xo/D0UrSpUwjP4E/TelOL/bscVjby/rK25Xa71SJl -pz/+0WatC7xrmYbvP33zGDLKe8bjq2RGlfgmadlVg3sslgf/WSxEo8bl6ancoWOA -WiFeIc9TVPC6b4nbqKqVz4vjccweGyBECMB6tkD9xOQ14R0WHNC8K47Wcdk= ------END CERTIFICATE----- - -# Issuer: CN=AddTrust External CA Root O=AddTrust AB OU=AddTrust External TTP Network -# Subject: CN=AddTrust External CA Root O=AddTrust AB OU=AddTrust External TTP Network -# Label: "AddTrust External Root" -# Serial: 1 -# MD5 Fingerprint: 1d:35:54:04:85:78:b0:3f:42:42:4d:bf:20:73:0a:3f -# SHA1 Fingerprint: 02:fa:f3:e2:91:43:54:68:60:78:57:69:4d:f5:e4:5b:68:85:18:68 -# SHA256 Fingerprint: 68:7f:a4:51:38:22:78:ff:f0:c8:b1:1f:8d:43:d5:76:67:1c:6e:b2:bc:ea:b4:13:fb:83:d9:65:d0:6d:2f:f2 ------BEGIN CERTIFICATE----- -MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU -MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs -IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290 -MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux -FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h -bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v -dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt -H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9 -uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX -mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX -a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN -E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0 -WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD -VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0 -Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU -cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx -IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN -AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH -YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5 -6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC -Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX -c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a -mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ= ------END CERTIFICATE----- - -# Issuer: CN=AddTrust Public CA Root O=AddTrust AB OU=AddTrust TTP Network -# Subject: CN=AddTrust Public CA Root O=AddTrust AB OU=AddTrust TTP Network -# Label: "AddTrust Public Services Root" -# Serial: 1 -# MD5 Fingerprint: c1:62:3e:23:c5:82:73:9c:03:59:4b:2b:e9:77:49:7f -# SHA1 Fingerprint: 2a:b6:28:48:5e:78:fb:f3:ad:9e:79:10:dd:6b:df:99:72:2c:96:e5 -# SHA256 Fingerprint: 07:91:ca:07:49:b2:07:82:aa:d3:c7:d7:bd:0c:df:c9:48:58:35:84:3e:b2:d7:99:60:09:ce:43:ab:6c:69:27 ------BEGIN CERTIFICATE----- -MIIEFTCCAv2gAwIBAgIBATANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJTRTEU -MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3 -b3JrMSAwHgYDVQQDExdBZGRUcnVzdCBQdWJsaWMgQ0EgUm9vdDAeFw0wMDA1MzAx -MDQxNTBaFw0yMDA1MzAxMDQxNTBaMGQxCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtB -ZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5ldHdvcmsxIDAeBgNV -BAMTF0FkZFRydXN0IFB1YmxpYyBDQSBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOC -AQ8AMIIBCgKCAQEA6Rowj4OIFMEg2Dybjxt+A3S72mnTRqX4jsIMEZBRpS9mVEBV -6tsfSlbunyNu9DnLoblv8n75XYcmYZ4c+OLspoH4IcUkzBEMP9smcnrHAZcHF/nX -GCwwfQ56HmIexkvA/X1id9NEHif2P0tEs7c42TkfYNVRknMDtABp4/MUTu7R3AnP -dzRGULD4EfL+OHn3Bzn+UZKXC1sIXzSGAa2Il+tmzV7R/9x98oTaunet3IAIx6eH -1lWfl2royBFkuucZKT8Rs3iQhCBSWxHveNCD9tVIkNAwHM+A+WD+eeSI8t0A65RF -62WUaUC6wNW0uLp9BBGo6zEFlpROWCGOn9Bg/QIDAQABo4HRMIHOMB0GA1UdDgQW -BBSBPjfYkrAfd59ctKtzquf2NGAv+jALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/BAUw -AwEB/zCBjgYDVR0jBIGGMIGDgBSBPjfYkrAfd59ctKtzquf2NGAv+qFopGYwZDEL -MAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQLExRBZGRU -cnVzdCBUVFAgTmV0d29yazEgMB4GA1UEAxMXQWRkVHJ1c3QgUHVibGljIENBIFJv -b3SCAQEwDQYJKoZIhvcNAQEFBQADggEBAAP3FUr4JNojVhaTdt02KLmuG7jD8WS6 -IBh4lSknVwW8fCr0uVFV2ocC3g8WFzH4qnkuCRO7r7IgGRLlk/lL+YPoRNWyQSW/ -iHVv/xD8SlTQX/D67zZzfRs2RcYhbbQVuE7PnFylPVoAjgbjPGsye/Kf8Lb93/Ao -GEjwxrzQvzSAlsJKsW2Ox5BF3i9nrEUEo3rcVZLJR2bYGozH7ZxOmuASu7VqTITh -4SINhwBk/ox9Yjllpu9CtoAlEmEBqCQTcAARJl/6NVDFSMwGR+gn2HCNX2TmoUQm -XiLsks3/QppEIW1cxeMiHV9HEufOX1362KqxMy3ZdvJOOjMMK7MtkAY= ------END CERTIFICATE----- - -# Issuer: CN=AddTrust Qualified CA Root O=AddTrust AB OU=AddTrust TTP Network -# Subject: CN=AddTrust Qualified CA Root O=AddTrust AB OU=AddTrust TTP Network -# Label: "AddTrust Qualified Certificates Root" -# Serial: 1 -# MD5 Fingerprint: 27:ec:39:47:cd:da:5a:af:e2:9a:01:65:21:a9:4c:bb -# SHA1 Fingerprint: 4d:23:78:ec:91:95:39:b5:00:7f:75:8f:03:3b:21:1e:c5:4d:8b:cf -# SHA256 Fingerprint: 80:95:21:08:05:db:4b:bc:35:5e:44:28:d8:fd:6e:c2:cd:e3:ab:5f:b9:7a:99:42:98:8e:b8:f4:dc:d0:60:16 ------BEGIN CERTIFICATE----- -MIIEHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJTRTEU -MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3 -b3JrMSMwIQYDVQQDExpBZGRUcnVzdCBRdWFsaWZpZWQgQ0EgUm9vdDAeFw0wMDA1 -MzAxMDQ0NTBaFw0yMDA1MzAxMDQ0NTBaMGcxCzAJBgNVBAYTAlNFMRQwEgYDVQQK -EwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5ldHdvcmsxIzAh -BgNVBAMTGkFkZFRydXN0IFF1YWxpZmllZCBDQSBSb290MIIBIjANBgkqhkiG9w0B -AQEFAAOCAQ8AMIIBCgKCAQEA5B6a/twJWoekn0e+EV+vhDTbYjx5eLfpMLXsDBwq -xBb/4Oxx64r1EW7tTw2R0hIYLUkVAcKkIhPHEWT/IhKauY5cLwjPcWqzZwFZ8V1G -87B4pfYOQnrjfxvM0PC3KP0q6p6zsLkEqv32x7SxuCqg+1jxGaBvcCV+PmlKfw8i -2O+tCBGaKZnhqkRFmhJePp1tUvznoD1oL/BLcHwTOK28FSXx1s6rosAx1i+f4P8U -WfyEk9mHfExUE+uf0S0R+Bg6Ot4l2ffTQO2kBhLEO+GRwVY18BTcZTYJbqukB8c1 -0cIDMzZbdSZtQvESa0NvS3GU+jQd7RNuyoB/mC9suWXY6QIDAQABo4HUMIHRMB0G -A1UdDgQWBBQ5lYtii1zJ1IC6WA+XPxUIQ8yYpzALBgNVHQ8EBAMCAQYwDwYDVR0T -AQH/BAUwAwEB/zCBkQYDVR0jBIGJMIGGgBQ5lYtii1zJ1IC6WA+XPxUIQ8yYp6Fr -pGkwZzELMAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQL -ExRBZGRUcnVzdCBUVFAgTmV0d29yazEjMCEGA1UEAxMaQWRkVHJ1c3QgUXVhbGlm -aWVkIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBABmrder4i2VhlRO6aQTv -hsoToMeqT2QbPxj2qC0sVY8FtzDqQmodwCVRLae/DLPt7wh/bDxGGuoYQ992zPlm -hpwsaPXpF/gxsxjE1kh9I0xowX67ARRvxdlu3rsEQmr49lx95dr6h+sNNVJn0J6X -dgWTP5XHAeZpVTh/EGGZyeNfpso+gmNIquIISD6q8rKFYqa0p9m9N5xotS1WfbC3 -P6CxB9bpT9zeRXEwMn8bLgn5v1Kh7sKAPgZcLlVAwRv1cEWw3F369nJad9Jjzc9Y -iQBCYz95OdBEsIJuQRno3eDBiFrRHnGTHyQwdOUeqN48Jzd/g66ed8/wMLH/S5no -xqE= ------END CERTIFICATE----- - -# 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=GeoTrust Global CA O=GeoTrust Inc. -# Subject: CN=GeoTrust Global CA O=GeoTrust Inc. -# Label: "GeoTrust Global CA" -# Serial: 144470 -# MD5 Fingerprint: f7:75:ab:29:fb:51:4e:b7:77:5e:ff:05:3c:99:8e:f5 -# SHA1 Fingerprint: de:28:f4:a4:ff:e5:b9:2f:a3:c5:03:d1:a3:49:a7:f9:96:2a:82:12 -# SHA256 Fingerprint: ff:85:6a:2d:25:1d:cd:88:d3:66:56:f4:50:12:67:98:cf:ab:aa:de:40:79:9c:72:2d:e4:d2:b5:db:36:a7:3a ------BEGIN CERTIFICATE----- -MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT -MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i -YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG -EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg -R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9 -9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq -fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv -iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU -1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+ -bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW -MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA -ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l -uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn -Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS -tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF -PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un -hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV -5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw== ------END CERTIFICATE----- - -# Issuer: CN=GeoTrust Global CA 2 O=GeoTrust Inc. -# Subject: CN=GeoTrust Global CA 2 O=GeoTrust Inc. -# Label: "GeoTrust Global CA 2" -# Serial: 1 -# MD5 Fingerprint: 0e:40:a7:6c:de:03:5d:8f:d1:0f:e4:d1:8d:f9:6c:a9 -# SHA1 Fingerprint: a9:e9:78:08:14:37:58:88:f2:05:19:b0:6d:2b:0d:2b:60:16:90:7d -# SHA256 Fingerprint: ca:2d:82:a0:86:77:07:2f:8a:b6:76:4f:f0:35:67:6c:fe:3e:5e:32:5e:01:21:72:df:3f:92:09:6d:b7:9b:85 ------BEGIN CERTIFICATE----- -MIIDZjCCAk6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBEMQswCQYDVQQGEwJVUzEW -MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFs -IENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMTkwMzA0MDUwMDAwWjBEMQswCQYDVQQG -EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3Qg -R2xvYmFsIENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDvPE1A -PRDfO1MA4Wf+lGAVPoWI8YkNkMgoI5kF6CsgncbzYEbYwbLVjDHZ3CB5JIG/NTL8 -Y2nbsSpr7iFY8gjpeMtvy/wWUsiRxP89c96xPqfCfWbB9X5SJBri1WeR0IIQ13hL -TytCOb1kLUCgsBDTOEhGiKEMuzozKmKY+wCdE1l/bztyqu6mD4b5BWHqZ38MN5aL -5mkWRxHCJ1kDs6ZgwiFAVvqgx306E+PsV8ez1q6diYD3Aecs9pYrEw15LNnA5IZ7 -S4wMcoKK+xfNAGw6EzywhIdLFnopsk/bHdQL82Y3vdj2V7teJHq4PIu5+pIaGoSe -2HSPqht/XvT+RSIhAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE -FHE4NvICMVNHK266ZUapEBVYIAUJMB8GA1UdIwQYMBaAFHE4NvICMVNHK266ZUap -EBVYIAUJMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQUFAAOCAQEAA/e1K6td -EPx7srJerJsOflN4WT5CBP51o62sgU7XAotexC3IUnbHLB/8gTKY0UvGkpMzNTEv -/NgdRN3ggX+d6YvhZJFiCzkIjKx0nVnZellSlxG5FntvRdOW2TF9AjYPnDtuzywN -A0ZF66D0f0hExghAzN4bcLUprbqLOzRldRtxIR0sFAqwlpW41uryZfspuk/qkZN0 -abby/+Ea0AzRdoXLiiW9l14sbxWZJue2Kf8i7MkCx1YAzUm5s2x7UwQa4qjJqhIF -I8LO57sEAszAR6LkxCkvW0VXiVHuPOtSCP8HNR6fNWpHSlaY0VqFH4z1Ir+rzoPz -4iIprn2DQKi6bA== ------END CERTIFICATE----- - -# Issuer: CN=GeoTrust Universal CA O=GeoTrust Inc. -# Subject: CN=GeoTrust Universal CA O=GeoTrust Inc. -# Label: "GeoTrust Universal CA" -# Serial: 1 -# MD5 Fingerprint: 92:65:58:8b:a2:1a:31:72:73:68:5c:b4:a5:7a:07:48 -# SHA1 Fingerprint: e6:21:f3:35:43:79:05:9a:4b:68:30:9d:8a:2f:74:22:15:87:ec:79 -# SHA256 Fingerprint: a0:45:9b:9f:63:b2:25:59:f5:fa:5d:4c:6d:b3:f9:f7:2f:f1:93:42:03:35:78:f0:73:bf:1d:1b:46:cb:b9:12 ------BEGIN CERTIFICATE----- -MIIFaDCCA1CgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJVUzEW -MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgVW5pdmVy -c2FsIENBMB4XDTA0MDMwNDA1MDAwMFoXDTI5MDMwNDA1MDAwMFowRTELMAkGA1UE -BhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xHjAcBgNVBAMTFUdlb1RydXN0 -IFVuaXZlcnNhbCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKYV -VaCjxuAfjJ0hUNfBvitbtaSeodlyWL0AG0y/YckUHUWCq8YdgNY96xCcOq9tJPi8 -cQGeBvV8Xx7BDlXKg5pZMK4ZyzBIle0iN430SppyZj6tlcDgFgDgEB8rMQ7XlFTT -QjOgNB0eRXbdT8oYN+yFFXoZCPzVx5zw8qkuEKmS5j1YPakWaDwvdSEYfyh3peFh -F7em6fgemdtzbvQKoiFs7tqqhZJmr/Z6a4LauiIINQ/PQvE1+mrufislzDoR5G2v -c7J2Ha3QsnhnGqQ5HFELZ1aD/ThdDc7d8Lsrlh/eezJS/R27tQahsiFepdaVaH/w -mZ7cRQg+59IJDTWU3YBOU5fXtQlEIGQWFwMCTFMNaN7VqnJNk22CDtucvc+081xd -VHppCZbW2xHBjXWotM85yM48vCR85mLK4b19p71XZQvk/iXttmkQ3CgaRr0BHdCX -teGYO8A3ZNY9lO4L4fUorgtWv3GLIylBjobFS1J72HGrH4oVpjuDWtdYAVHGTEHZ -f9hBZ3KiKN9gg6meyHv8U3NyWfWTehd2Ds735VzZC1U0oqpbtWpU5xPKV+yXbfRe -Bi9Fi1jUIxaS5BZuKGNZMN9QAZxjiRqf2xeUgnA3wySemkfWWspOqGmJch+RbNt+ -nhutxx9z3SxPGWX9f5NAEC7S8O08ni4oPmkmM8V7AgMBAAGjYzBhMA8GA1UdEwEB -/wQFMAMBAf8wHQYDVR0OBBYEFNq7LqqwDLiIJlF0XG0D08DYj3rWMB8GA1UdIwQY -MBaAFNq7LqqwDLiIJlF0XG0D08DYj3rWMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG -9w0BAQUFAAOCAgEAMXjmx7XfuJRAyXHEqDXsRh3ChfMoWIawC/yOsjmPRFWrZIRc -aanQmjg8+uUfNeVE44B5lGiku8SfPeE0zTBGi1QrlaXv9z+ZhP015s8xxtxqv6fX -IwjhmF7DWgh2qaavdy+3YL1ERmrvl/9zlcGO6JP7/TG37FcREUWbMPEaiDnBTzyn -ANXH/KttgCJwpQzgXQQpAvvLoJHRfNbDflDVnVi+QTjruXU8FdmbyUqDWcDaU/0z -uzYYm4UPFd3uLax2k7nZAY1IEKj79TiG8dsKxr2EoyNB3tZ3b4XUhRxQ4K5RirqN -Pnbiucon8l+f725ZDQbYKxek0nxru18UGkiPGkzns0ccjkxFKyDuSN/n3QmOGKja -QI2SJhFTYXNd673nxE0pN2HrrDktZy4W1vUAg4WhzH92xH3kt0tm7wNFYGm2DFKW -koRepqO1pD4r2czYG0eq8kTaT/kD6PAUyz/zg97QwVTjt+gKN02LIFkDMBmhLMi9 -ER/frslKxfMnZmaGrGiR/9nmUxwPi1xpZQomyB40w11Re9epnAahNt3ViZS82eQt -DF4JbAiXfKM9fJP/P6EUp8+1Xevb2xzEdt+Iub1FBZUbrvxGakyvSOPOrg/Sfuvm -bJxPgWp6ZKy7PtXny3YuxadIwVyQD8vIP/rmMuGNG2+k5o7Y+SlIis5z/iw= ------END CERTIFICATE----- - -# Issuer: CN=GeoTrust Universal CA 2 O=GeoTrust Inc. -# Subject: CN=GeoTrust Universal CA 2 O=GeoTrust Inc. -# Label: "GeoTrust Universal CA 2" -# Serial: 1 -# MD5 Fingerprint: 34:fc:b8:d0:36:db:9e:14:b3:c2:f2:db:8f:e4:94:c7 -# SHA1 Fingerprint: 37:9a:19:7b:41:85:45:35:0c:a6:03:69:f3:3c:2e:af:47:4f:20:79 -# SHA256 Fingerprint: a0:23:4f:3b:c8:52:7c:a5:62:8e:ec:81:ad:5d:69:89:5d:a5:68:0d:c9:1d:1c:b8:47:7f:33:f8:78:b9:5b:0b ------BEGIN CERTIFICATE----- -MIIFbDCCA1SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBHMQswCQYDVQQGEwJVUzEW -MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVy -c2FsIENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMjkwMzA0MDUwMDAwWjBHMQswCQYD -VQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1 -c3QgVW5pdmVyc2FsIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC -AQCzVFLByT7y2dyxUxpZKeexw0Uo5dfR7cXFS6GqdHtXr0om/Nj1XqduGdt0DE81 -WzILAePb63p3NeqqWuDW6KFXlPCQo3RWlEQwAx5cTiuFJnSCegx2oG9NzkEtoBUG -FF+3Qs17j1hhNNwqCPkuwwGmIkQcTAeC5lvO0Ep8BNMZcyfwqph/Lq9O64ceJHdq -XbboW0W63MOhBW9Wjo8QJqVJwy7XQYci4E+GymC16qFjwAGXEHm9ADwSbSsVsaxL -se4YuU6W3Nx2/zu+z18DwPw76L5GG//aQMJS9/7jOvdqdzXQ2o3rXhhqMcceujwb -KNZrVMaqW9eiLBsZzKIC9ptZvTdrhrVtgrrY6slWvKk2WP0+GfPtDCapkzj4T8Fd -IgbQl+rhrcZV4IErKIM6+vR7IVEAvlI4zs1meaj0gVbi0IMJR1FbUGrP20gaXT73 -y/Zl92zxlfgCOzJWgjl6W70viRu/obTo/3+NjN8D8WBOWBFM66M/ECuDmgFz2ZRt -hAAnZqzwcEAJQpKtT5MNYQlRJNiS1QuUYbKHsu3/mjX/hVTK7URDrBs8FmtISgoc -QIgfksILAAX/8sgCSqSqqcyZlpwvWOB94b67B9xfBHJcMTTD7F8t4D1kkCLm0ey4 -Lt1ZrtmhN79UNdxzMk+MBB4zsslG8dhcyFVQyWi9qLo2CQIDAQABo2MwYTAPBgNV -HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAfBgNV -HSMEGDAWgBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAOBgNVHQ8BAf8EBAMCAYYwDQYJ -KoZIhvcNAQEFBQADggIBAGbBxiPz2eAubl/oz66wsCVNK/g7WJtAJDday6sWSf+z -dXkzoS9tcBc0kf5nfo/sm+VegqlVHy/c1FEHEv6sFj4sNcZj/NwQ6w2jqtB8zNHQ -L1EuxBRa3ugZ4T7GzKQp5y6EqgYweHZUcyiYWTjgAA1i00J9IZ+uPTqM1fp3DRgr -Fg5fNuH8KrUwJM/gYwx7WBr+mbpCErGR9Hxo4sjoryzqyX6uuyo9DRXcNJW2GHSo -ag/HtPQTxORb7QrSpJdMKu0vbBKJPfEncKpqA1Ihn0CoZ1Dy81of398j9tx4TuaY -T1U6U+Pv8vSfx3zYWK8pIpe44L2RLrB27FcRz+8pRPPphXpgY+RdM4kX2TGq2tbz -GDVyz4crL2MjhF2EjD9XoIj8mZEoJmmZ1I+XRL6O1UixpCgp8RW04eWe3fiPpm8m -1wk8OhwRDqZsN/etRIcsKMfYdIKz0G9KV7s1KSegi+ghp4dkNl3M2Basx7InQJJV -OCiNUW7dFGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH -6aLcr34YEoP9VhdBLtUpgn2Z9DH2canPLAEnpQW5qrJITirvn5NSUZU8UnOOVkwX -QMAJKOSLakhT2+zNVVXxxvjpoixMptEmX36vWkzaH6byHCx+rgIW0lbQL1dTR+iS ------END CERTIFICATE----- - -# Issuer: CN=America Online Root Certification Authority 1 O=America Online Inc. -# Subject: CN=America Online Root Certification Authority 1 O=America Online Inc. -# Label: "America Online Root Certification Authority 1" -# Serial: 1 -# MD5 Fingerprint: 14:f1:08:ad:9d:fa:64:e2:89:e7:1c:cf:a8:ad:7d:5e -# SHA1 Fingerprint: 39:21:c1:15:c1:5d:0e:ca:5c:cb:5b:c4:f0:7d:21:d8:05:0b:56:6a -# SHA256 Fingerprint: 77:40:73:12:c6:3a:15:3d:5b:c0:0b:4e:51:75:9c:df:da:c2:37:dc:2a:33:b6:79:46:e9:8e:9b:fa:68:0a:e3 ------BEGIN CERTIFICATE----- -MIIDpDCCAoygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEc -MBoGA1UEChMTQW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBP -bmxpbmUgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAxMB4XDTAyMDUyODA2 -MDAwMFoXDTM3MTExOTIwNDMwMFowYzELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0Ft -ZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2EgT25saW5lIFJvb3Qg -Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMTCCASIwDQYJKoZIhvcNAQEBBQADggEP -ADCCAQoCggEBAKgv6KRpBgNHw+kqmP8ZonCaxlCyfqXfaE0bfA+2l2h9LaaLl+lk -hsmj76CGv2BlnEtUiMJIxUo5vxTjWVXlGbR0yLQFOVwWpeKVBeASrlmLojNoWBym -1BW32J/X3HGrfpq/m44zDyL9Hy7nBzbvYjnF3cu6JRQj3gzGPTzOggjmZj7aUTsW -OqMFf6Dch9Wc/HKpoH145LcxVR5lu9RhsCFg7RAycsWSJR74kEoYeEfffjA3PlAb -2xzTa5qGUwew76wGePiEmf4hjUyAtgyC9mZweRrTT6PP8c9GsEsPPt2IYriMqQko -O3rHl+Ee5fSfwMCuJKDIodkP1nsmgmkyPacCAwEAAaNjMGEwDwYDVR0TAQH/BAUw -AwEB/zAdBgNVHQ4EFgQUAK3Zo/Z59m50qX8zPYEX10zPM94wHwYDVR0jBBgwFoAU -AK3Zo/Z59m50qX8zPYEX10zPM94wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB -BQUAA4IBAQB8itEfGDeC4Liwo+1WlchiYZwFos3CYiZhzRAW18y0ZTTQEYqtqKkF -Zu90821fnZmv9ov761KyBZiibyrFVL0lvV+uyIbqRizBs73B6UlwGBaXCBOMIOAb -LjpHyx7kADCVW/RFo8AasAFOq73AI25jP4BKxQft3OJvx8Fi8eNy1gTIdGcL+oir -oQHIb/AUr9KZzVGTfu0uOMe9zkZQPXLjeSWdm4grECDdpbgyn43gKd8hdIaC2y+C -MMbHNYaz+ZZfRtsMRf3zUMNvxsNIrUam4SdHCh0Om7bCd39j8uB9Gr784N/Xx6ds -sPmuujz9dLQR6FgNgLzTqIA6me11zEZ7 ------END CERTIFICATE----- - -# Issuer: CN=America Online Root Certification Authority 2 O=America Online Inc. -# Subject: CN=America Online Root Certification Authority 2 O=America Online Inc. -# Label: "America Online Root Certification Authority 2" -# Serial: 1 -# MD5 Fingerprint: d6:ed:3c:ca:e2:66:0f:af:10:43:0d:77:9b:04:09:bf -# SHA1 Fingerprint: 85:b5:ff:67:9b:0c:79:96:1f:c8:6e:44:22:00:46:13:db:17:92:84 -# SHA256 Fingerprint: 7d:3b:46:5a:60:14:e5:26:c0:af:fc:ee:21:27:d2:31:17:27:ad:81:1c:26:84:2d:00:6a:f3:73:06:cc:80:bd ------BEGIN CERTIFICATE----- -MIIFpDCCA4ygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEc -MBoGA1UEChMTQW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBP -bmxpbmUgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAyMB4XDTAyMDUyODA2 -MDAwMFoXDTM3MDkyOTE0MDgwMFowYzELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0Ft -ZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2EgT25saW5lIFJvb3Qg -Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIP -ADCCAgoCggIBAMxBRR3pPU0Q9oyxQcngXssNt79Hc9PwVU3dxgz6sWYFas14tNwC -206B89enfHG8dWOgXeMHDEjsJcQDIPT/DjsS/5uN4cbVG7RtIuOx238hZK+GvFci -KtZHgVdEglZTvYYUAQv8f3SkWq7xuhG1m1hagLQ3eAkzfDJHA1zEpYNI9FdWboE2 -JxhP7JsowtS013wMPgwr38oE18aO6lhOqKSlGBxsRZijQdEt0sdtjRnxrXm3gT+9 -BoInLRBYBbV4Bbkv2wxrkJB+FFk4u5QkE+XRnRTf04JNRvCAOVIyD+OEsnpD8l7e -Xz8d3eOyG6ChKiMDbi4BFYdcpnV1x5dhvt6G3NRI270qv0pV2uh9UPu0gBe4lL8B -PeraunzgWGcXuVjgiIZGZ2ydEEdYMtA1fHkqkKJaEBEjNa0vzORKW6fIJ/KD3l67 -Xnfn6KVuY8INXWHQjNJsWiEOyiijzirplcdIz5ZvHZIlyMbGwcEMBawmxNJ10uEq -Z8A9W6Wa6897GqidFEXlD6CaZd4vKL3Ob5Rmg0gp2OpljK+T2WSfVVcmv2/LNzGZ -o2C7HK2JNDJiuEMhBnIMoVxtRsX6Kc8w3onccVvdtjc+31D1uAclJuW8tf48ArO3 -+L5DwYcRlJ4jbBeKuIonDFRH8KmzwICMoCfrHRnjB453cMor9H124HhnAgMBAAGj -YzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFE1FwWg4u3OpaaEg5+31IqEj -FNeeMB8GA1UdIwQYMBaAFE1FwWg4u3OpaaEg5+31IqEjFNeeMA4GA1UdDwEB/wQE -AwIBhjANBgkqhkiG9w0BAQUFAAOCAgEAZ2sGuV9FOypLM7PmG2tZTiLMubekJcmn -xPBUlgtk87FYT15R/LKXeydlwuXK5w0MJXti4/qftIe3RUavg6WXSIylvfEWK5t2 -LHo1YGwRgJfMqZJS5ivmae2p+DYtLHe/YUjRYwu5W1LtGLBDQiKmsXeu3mnFzccc -obGlHBD7GL4acN3Bkku+KVqdPzW+5X1R+FXgJXUjhx5c3LqdsKyzadsXg8n33gy8 -CNyRnqjQ1xU3c6U1uPx+xURABsPr+CKAXEfOAuMRn0T//ZoyzH1kUQ7rVyZ2OuMe -IjzCpjbdGe+n/BLzJsBZMYVMnNjP36TMzCmT/5RtdlwTCJfy7aULTd3oyWgOZtMA -DjMSW7yV5TKQqLPGbIOtd+6Lfn6xqavT4fG2wLHqiMDn05DpKJKUe2h7lyoKZy2F -AjgQ5ANh1NolNscIWC2hp1GvMApJ9aZphwctREZ2jirlmjvXGKL8nDgQzMY70rUX -Om/9riW99XJZZLF0KjhfGEzfz3EEWjbUvy+ZnOjZurGV5gJLIaFb1cFPj65pbVPb -AZO1XB4Y3WRayhgoPmMEEf0cjQAPuDffZ4qdZqkCapH/E8ovXYO8h5Ns3CRRFgQl -Zvqz2cK6Kb6aSDiCmfS/O0oxGfm/jiEzFMpPVF/7zvuPcX/9XhmgD0uRuMRUvAaw -RY8mkaKO/qk= ------END CERTIFICATE----- - -# Issuer: CN=AAA Certificate Services O=Comodo CA Limited -# Subject: CN=AAA Certificate Services O=Comodo CA Limited -# Label: "Comodo AAA Services root" -# Serial: 1 -# MD5 Fingerprint: 49:79:04:b0:eb:87:19:ac:47:b0:bc:11:51:9b:74:d0 -# SHA1 Fingerprint: d1:eb:23:a4:6d:17:d6:8f:d9:25:64:c2:f1:f1:60:17:64:d8:e3:49 -# SHA256 Fingerprint: d7:a7:a0:fb:5d:7e:27:31:d7:71:e9:48:4e:bc:de:f7:1d:5f:0c:3e:0a:29:48:78:2b:c8:3e:e0:ea:69:9e:f4 ------BEGIN CERTIFICATE----- -MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb -MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow -GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj -YXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL -MAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE -BwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM -GEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP -ADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua -BtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe -3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4 -YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR -rOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm -ez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU -oBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF -MAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v -QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t -b2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF -AAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q -GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz -Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2 -G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi -l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3 -smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== ------END CERTIFICATE----- - -# Issuer: CN=Secure Certificate Services O=Comodo CA Limited -# Subject: CN=Secure Certificate Services O=Comodo CA Limited -# Label: "Comodo Secure Services root" -# Serial: 1 -# MD5 Fingerprint: d3:d9:bd:ae:9f:ac:67:24:b3:c8:1b:52:e1:b9:a9:bd -# SHA1 Fingerprint: 4a:65:d5:f4:1d:ef:39:b8:b8:90:4a:4a:d3:64:81:33:cf:c7:a1:d1 -# SHA256 Fingerprint: bd:81:ce:3b:4f:65:91:d1:1a:67:b5:fc:7a:47:fd:ef:25:52:1b:f9:aa:4e:18:b9:e3:df:2e:34:a7:80:3b:e8 ------BEGIN CERTIFICATE----- -MIIEPzCCAyegAwIBAgIBATANBgkqhkiG9w0BAQUFADB+MQswCQYDVQQGEwJHQjEb -MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow -GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEkMCIGA1UEAwwbU2VjdXJlIENlcnRp -ZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVow -fjELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G -A1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxJDAiBgNV -BAMMG1NlY3VyZSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEB -BQADggEPADCCAQoCggEBAMBxM4KK0HDrc4eCQNUd5MvJDkKQ+d40uaG6EfQlhfPM -cm3ye5drswfxdySRXyWP9nQ95IDC+DwN879A6vfIUtFyb+/Iq0G4bi4XKpVpDM3S -HpR7LZQdqnXXs5jLrLxkU0C8j6ysNstcrbvd4JQX7NFc0L/vpZXJkMWwrPsbQ996 -CF23uPJAGysnnlDOXmWCiIxe004MeuoIkbY2qitC++rCoznl2yY4rYsK7hljxxwk -3wN42ubqwUcaCwtGCd0C/N7Lh1/XMGNooa7cMqG6vv5Eq2i2pRcV/b3Vp6ea5EQz -6YiO/O1R65NxTq0B50SOqy3LqP4BSUjwwN3HaNiS/j0CAwEAAaOBxzCBxDAdBgNV -HQ4EFgQUPNiTiMLAggnMAZkGkyDpnnAJY08wDgYDVR0PAQH/BAQDAgEGMA8GA1Ud -EwEB/wQFMAMBAf8wgYEGA1UdHwR6MHgwO6A5oDeGNWh0dHA6Ly9jcmwuY29tb2Rv -Y2EuY29tL1NlY3VyZUNlcnRpZmljYXRlU2VydmljZXMuY3JsMDmgN6A1hjNodHRw -Oi8vY3JsLmNvbW9kby5uZXQvU2VjdXJlQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmww -DQYJKoZIhvcNAQEFBQADggEBAIcBbSMdflsXfcFhMs+P5/OKlFlm4J4oqF7Tt/Q0 -5qo5spcWxYJvMqTpjOev/e/C6LlLqqP05tqNZSH7uoDrJiiFGv45jN5bBAS0VPmj -Z55B+glSzAVIqMk/IQQezkhr/IXownuvf7fM+F86/TXGDe+X3EyrEeFryzHRbPtI -gKvcnDe4IRRLDXE97IMzbtFuMhbsmMcWi1mmNKsFVy2T96oTy9IT4rcuO81rUBcJ -aD61JlfutuC23bkpgHl9j6PwpCikFcSF9CfUa7/lXORlAnZUtOM3ZiTTGWHIUhDl -izeauan5Hb/qmZJhlv8BzaFfDbxxvA6sCx1HRR3B7Hzs/Sk= ------END CERTIFICATE----- - -# Issuer: CN=Trusted Certificate Services O=Comodo CA Limited -# Subject: CN=Trusted Certificate Services O=Comodo CA Limited -# Label: "Comodo Trusted Services root" -# Serial: 1 -# MD5 Fingerprint: 91:1b:3f:6e:cd:9e:ab:ee:07:fe:1f:71:d2:b3:61:27 -# SHA1 Fingerprint: e1:9f:e3:0e:8b:84:60:9e:80:9b:17:0d:72:a8:c5:ba:6e:14:09:bd -# SHA256 Fingerprint: 3f:06:e5:56:81:d4:96:f5:be:16:9e:b5:38:9f:9f:2b:8f:f6:1e:17:08:df:68:81:72:48:49:cd:5d:27:cb:69 ------BEGIN CERTIFICATE----- -MIIEQzCCAyugAwIBAgIBATANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJHQjEb -MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow -GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDElMCMGA1UEAwwcVHJ1c3RlZCBDZXJ0 -aWZpY2F0ZSBTZXJ2aWNlczAeFw0wNDAxMDEwMDAwMDBaFw0yODEyMzEyMzU5NTla -MH8xCzAJBgNVBAYTAkdCMRswGQYDVQQIDBJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO -BgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoMEUNvbW9kbyBDQSBMaW1pdGVkMSUwIwYD -VQQDDBxUcnVzdGVkIENlcnRpZmljYXRlIFNlcnZpY2VzMIIBIjANBgkqhkiG9w0B -AQEFAAOCAQ8AMIIBCgKCAQEA33FvNlhTWvI2VFeAxHQIIO0Yfyod5jWaHiWsnOWW -fnJSoBVC21ndZHoa0Lh73TkVvFVIxO06AOoxEbrycXQaZ7jPM8yoMa+j49d/vzMt -TGo87IvDktJTdyR0nAducPy9C1t2ul/y/9c3S0pgePfw+spwtOpZqqPOSC+pw7IL -fhdyFgymBwwbOM/JYrc/oJOlh0Hyt3BAd9i+FHzjqMB6juljatEPmsbS9Is6FARW -1O24zG71++IsWL1/T2sr92AkWCTOJu80kTrV44HQsvAEAtdbtz6SrGsSivnkBbA7 -kUlcsutT6vifR4buv5XAwAaf0lteERv0xwQ1KdJVXOTt6wIDAQABo4HJMIHGMB0G -A1UdDgQWBBTFe1i97doladL3WRaoszLAeydb9DAOBgNVHQ8BAf8EBAMCAQYwDwYD -VR0TAQH/BAUwAwEB/zCBgwYDVR0fBHwwejA8oDqgOIY2aHR0cDovL2NybC5jb21v -ZG9jYS5jb20vVHJ1c3RlZENlcnRpZmljYXRlU2VydmljZXMuY3JsMDqgOKA2hjRo -dHRwOi8vY3JsLmNvbW9kby5uZXQvVHJ1c3RlZENlcnRpZmljYXRlU2VydmljZXMu -Y3JsMA0GCSqGSIb3DQEBBQUAA4IBAQDIk4E7ibSvuIQSTI3S8NtwuleGFTQQuS9/ -HrCoiWChisJ3DFBKmwCL2Iv0QeLQg4pKHBQGsKNoBXAxMKdTmw7pSqBYaWcOrp32 -pSxBvzwGa+RZzG0Q8ZZvH9/0BAKkn0U+yNj6NkZEUD+Cl5EfKNsYEYwq5GWDVxIS -jBc/lDb+XbDABHcTuPQV1T84zJQ6VdCsmPW6AF/ghhmBeC8owH7TzEIK9a5QoNE+ -xqFx7D+gIIxmOom0jtTYsU0lR+4viMi14QVFwL4Ucd56/Y57fU0IlqUSc/Atyjcn -dBInTMu2l+nZrghtWjlA3QVHdWpaIbOjGM9O9y5Xt5hwXsjEeLBi ------END CERTIFICATE----- - -# Issuer: CN=UTN - DATACorp SGC O=The USERTRUST Network OU=http://www.usertrust.com -# Subject: CN=UTN - DATACorp SGC O=The USERTRUST Network OU=http://www.usertrust.com -# Label: "UTN DATACorp SGC Root CA" -# Serial: 91374294542884689855167577680241077609 -# MD5 Fingerprint: b3:a5:3e:77:21:6d:ac:4a:c0:c9:fb:d5:41:3d:ca:06 -# SHA1 Fingerprint: 58:11:9f:0e:12:82:87:ea:50:fd:d9:87:45:6f:4f:78:dc:fa:d6:d4 -# SHA256 Fingerprint: 85:fb:2f:91:dd:12:27:5a:01:45:b6:36:53:4f:84:02:4a:d6:8b:69:b8:ee:88:68:4f:f7:11:37:58:05:b3:48 ------BEGIN CERTIFICATE----- -MIIEXjCCA0agAwIBAgIQRL4Mi1AAIbQR0ypoBqmtaTANBgkqhkiG9w0BAQUFADCB -kzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug -Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho -dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xGzAZBgNVBAMTElVUTiAtIERBVEFDb3Jw -IFNHQzAeFw05OTA2MjQxODU3MjFaFw0xOTA2MjQxOTA2MzBaMIGTMQswCQYDVQQG -EwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYD -VQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cu -dXNlcnRydXN0LmNvbTEbMBkGA1UEAxMSVVROIC0gREFUQUNvcnAgU0dDMIIBIjAN -BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3+5YEKIrblXEjr8uRgnn4AgPLit6 -E5Qbvfa2gI5lBZMAHryv4g+OGQ0SR+ysraP6LnD43m77VkIVni5c7yPeIbkFdicZ -D0/Ww5y0vpQZY/KmEQrrU0icvvIpOxboGqBMpsn0GFlowHDyUwDAXlCCpVZvNvlK -4ESGoE1O1kduSUrLZ9emxAW5jh70/P/N5zbgnAVssjMiFdC04MwXwLLA9P4yPykq -lXvY8qdOD1R8oQ2AswkDwf9c3V6aPryuvEeKaq5xyh+xKrhfQgUL7EYw0XILyulW -bfXv33i+Ybqypa4ETLyorGkVl73v67SMvzX41MPRKA5cOp9wGDMgd8SirwIDAQAB -o4GrMIGoMAsGA1UdDwQEAwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRT -MtGzz3/64PGgXYVOktKeRR20TzA9BgNVHR8ENjA0MDKgMKAuhixodHRwOi8vY3Js -LnVzZXJ0cnVzdC5jb20vVVROLURBVEFDb3JwU0dDLmNybDAqBgNVHSUEIzAhBggr -BgEFBQcDAQYKKwYBBAGCNwoDAwYJYIZIAYb4QgQBMA0GCSqGSIb3DQEBBQUAA4IB -AQAnNZcAiosovcYzMB4p/OL31ZjUQLtgyr+rFywJNn9Q+kHcrpY6CiM+iVnJowft -Gzet/Hy+UUla3joKVAgWRcKZsYfNjGjgaQPpxE6YsjuMFrMOoAyYUJuTqXAJyCyj -j98C5OBxOvG0I3KgqgHf35g+FFCgMSa9KOlaMCZ1+XtgHI3zzVAmbQQnmt/VDUVH -KWss5nbZqSl9Mt3JNjy9rjXxEZ4du5A/EkdOjtd+D2JzHVImOBwYSf0wdJrE5SIv -2MCN7ZF6TACPcn9d2t0bi0Vr591pl6jFVkwPDPafepE39peC4N1xaf92P2BNPM/3 -mfnGV/TJVTl4uix5yaaIK/QI ------END CERTIFICATE----- - -# Issuer: CN=UTN-USERFirst-Hardware O=The USERTRUST Network OU=http://www.usertrust.com -# Subject: CN=UTN-USERFirst-Hardware O=The USERTRUST Network OU=http://www.usertrust.com -# Label: "UTN USERFirst Hardware Root CA" -# Serial: 91374294542884704022267039221184531197 -# MD5 Fingerprint: 4c:56:41:e5:0d:bb:2b:e8:ca:a3:ed:18:08:ad:43:39 -# SHA1 Fingerprint: 04:83:ed:33:99:ac:36:08:05:87:22:ed:bc:5e:46:00:e3:be:f9:d7 -# SHA256 Fingerprint: 6e:a5:47:41:d0:04:66:7e:ed:1b:48:16:63:4a:a3:a7:9e:6e:4b:96:95:0f:82:79:da:fc:8d:9b:d8:81:21:37 ------BEGIN CERTIFICATE----- -MIIEdDCCA1ygAwIBAgIQRL4Mi1AAJLQR0zYq/mUK/TANBgkqhkiG9w0BAQUFADCB -lzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug -Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho -dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3Qt -SGFyZHdhcmUwHhcNOTkwNzA5MTgxMDQyWhcNMTkwNzA5MTgxOTIyWjCBlzELMAkG -A1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEe -MBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8v -d3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdh -cmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCx98M4P7Sof885glFn -0G2f0v9Y8+efK+wNiVSZuTiZFvfgIXlIwrthdBKWHTxqctU8EGc6Oe0rE81m65UJ -M6Rsl7HoxuzBdXmcRl6Nq9Bq/bkqVRcQVLMZ8Jr28bFdtqdt++BxF2uiiPsA3/4a -MXcMmgF6sTLjKwEHOG7DpV4jvEWbe1DByTCP2+UretNb+zNAHqDVmBe8i4fDidNd -oI6yqqr2jmmIBsX6iSHzCJ1pLgkzmykNRg+MzEk0sGlRvfkGzWitZky8PqxhvQqI -DsjfPe58BEydCl5rkdbux+0ojatNh4lz0G6k0B4WixThdkQDf2Os5M1JnMWS9Ksy -oUhbAgMBAAGjgbkwgbYwCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8wHQYD -VR0OBBYEFKFyXyYbKJhDlV0HN9WFlp1L0sNFMEQGA1UdHwQ9MDswOaA3oDWGM2h0 -dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNFUkZpcnN0LUhhcmR3YXJlLmNy -bDAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwUGCCsGAQUFBwMGBggrBgEF -BQcDBzANBgkqhkiG9w0BAQUFAAOCAQEARxkP3nTGmZev/K0oXnWO6y1n7k57K9cM -//bey1WiCuFMVGWTYGufEpytXoMs61quwOQt9ABjHbjAbPLPSbtNk28Gpgoiskli -CE7/yMgUsogWXecB5BKV5UU0s4tpvc+0hY91UZ59Ojg6FEgSxvunOxqNDYJAB+gE -CJChicsZUN/KHAG8HQQZexB2lzvukJDKxA4fFm517zP4029bHpbj4HR3dHuKom4t -3XbWOTCC8KucUvIqx69JXn7HaOWCgchqJ/kniCrVWFCVH/A7HFe7fRQ5YiuayZSS -KqMiDP+JJn1fIytH1xUdqWqeUQ0qUZ6B+dQ7XnASfxAynB67nfhmqA== ------END CERTIFICATE----- - -# Issuer: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com -# Subject: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com -# Label: "XRamp Global CA Root" -# Serial: 107108908803651509692980124233745014957 -# MD5 Fingerprint: a1:0b:44:b3:ca:10:d8:00:6e:9d:0f:d8:0f:92:0a:d1 -# SHA1 Fingerprint: b8:01:86:d1:eb:9c:86:a5:41:04:cf:30:54:f3:4c:52:b7:e5:58:c6 -# SHA256 Fingerprint: ce:cd:dc:90:50:99:d8:da:df:c5:b1:d2:09:b7:37:cb:e2:c1:8c:fb:2c:10:c0:ff:0b:cf:0d:32:86:fc:1a:a2 ------BEGIN CERTIFICATE----- -MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCB -gjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEk -MCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRY -UmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQxMTAxMTcx -NDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3 -dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2Vy -dmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB -dXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS6 -38eMpSe2OAtp87ZOqCwuIR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCP -KZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMxfoArtYzAQDsRhtDLooY2YKTVMIJt2W7Q -DxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FEzG+gSqmUsE3a56k0enI4 -qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqsAxcZZPRa -JSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNVi -PvryxS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0P -BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASs -jVy16bYbMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0 -eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQEwDQYJKoZIhvcNAQEFBQAD -ggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc/Kh4ZzXxHfAR -vbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt -qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLa -IR9NmXmd4c8nnxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSy -i6mx5O+aGtA9aZnuqCij4Tyz8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQ -O+7ETPTsJ3xCwnR8gooJybQDJbw= ------END CERTIFICATE----- - -# Issuer: O=The Go Daddy Group, Inc. OU=Go Daddy Class 2 Certification Authority -# Subject: O=The Go Daddy Group, Inc. OU=Go Daddy Class 2 Certification Authority -# Label: "Go Daddy Class 2 CA" -# Serial: 0 -# MD5 Fingerprint: 91:de:06:25:ab:da:fd:32:17:0c:bb:25:17:2a:84:67 -# SHA1 Fingerprint: 27:96:ba:e6:3f:18:01:e2:77:26:1b:a0:d7:77:70:02:8f:20:ee:e4 -# SHA256 Fingerprint: c3:84:6b:f2:4b:9e:93:ca:64:27:4c:0e:c6:7c:1e:cc:5e:02:4f:fc:ac:d2:d7:40:19:35:0e:81:fe:54:6a:e4 ------BEGIN CERTIFICATE----- -MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh -MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE -YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3 -MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo -ZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg -MiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN -ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA -PVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w -wdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi -EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY -avx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+ -YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE -sNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h -/t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5 -IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj -YXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD -ggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy -OO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P -TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ -HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER -dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf -ReYNnyicsbkqWletNw+vHX/bvZ8= ------END CERTIFICATE----- - -# Issuer: O=Starfield Technologies, Inc. OU=Starfield Class 2 Certification Authority -# Subject: O=Starfield Technologies, Inc. OU=Starfield Class 2 Certification Authority -# Label: "Starfield Class 2 CA" -# Serial: 0 -# MD5 Fingerprint: 32:4a:4b:bb:c8:63:69:9b:be:74:9a:c6:dd:1d:46:24 -# SHA1 Fingerprint: ad:7e:1c:28:b0:64:ef:8f:60:03:40:20:14:c3:d0:e3:37:0e:b5:8a -# SHA256 Fingerprint: 14:65:fa:20:53:97:b8:76:fa:a6:f0:a9:95:8e:55:90:e4:0f:cc:7f:aa:4f:b7:c2:c8:67:75:21:fb:5f:b6:58 ------BEGIN CERTIFICATE----- -MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl -MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp -U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw -NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE -ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp -ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3 -DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf -8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN -+lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0 -X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa -K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA -1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G -A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR -zt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0 -YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD -bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w -DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3 -L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D -eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl -xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp -VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY -WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q= ------END CERTIFICATE----- - -# Issuer: CN=StartCom Certification Authority O=StartCom Ltd. OU=Secure Digital Certificate Signing -# Subject: CN=StartCom Certification Authority O=StartCom Ltd. OU=Secure Digital Certificate Signing -# Label: "StartCom Certification Authority" -# Serial: 1 -# MD5 Fingerprint: 22:4d:8f:8a:fc:f7:35:c2:bb:57:34:90:7b:8b:22:16 -# SHA1 Fingerprint: 3e:2b:f7:f2:03:1b:96:f3:8c:e6:c4:d8:a8:5d:3e:2d:58:47:6a:0f -# SHA256 Fingerprint: c7:66:a9:be:f2:d4:07:1c:86:3a:31:aa:49:20:e8:13:b2:d1:98:60:8c:b7:b7:cf:e2:11:43:b8:36:df:09:ea ------BEGIN CERTIFICATE----- -MIIHyTCCBbGgAwIBAgIBATANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEW -MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg -Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh -dGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0NjM2WhcNMzYwOTE3MTk0NjM2WjB9 -MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMi -U2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3Rh -cnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA -A4ICDwAwggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZk -pMyONvg45iPwbm2xPN1yo4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rf -OQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/C -Ji/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/deMotHweXMAEtcnn6RtYT -Kqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt2PZE4XNi -HzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMM -Av+Z6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w -+2OqqGwaVLRcJXrJosmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+ -Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3 -Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVcUjyJthkqcwEKDwOzEmDyei+B -26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT37uMdBNSSwID -AQABo4ICUjCCAk4wDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAa4wHQYDVR0OBBYE -FE4L7xqkQFulF2mHMMo0aEPQQa7yMGQGA1UdHwRdMFswLKAqoCiGJmh0dHA6Ly9j -ZXJ0LnN0YXJ0Y29tLm9yZy9zZnNjYS1jcmwuY3JsMCugKaAnhiVodHRwOi8vY3Js -LnN0YXJ0Y29tLm9yZy9zZnNjYS1jcmwuY3JsMIIBXQYDVR0gBIIBVDCCAVAwggFM -BgsrBgEEAYG1NwEBATCCATswLwYIKwYBBQUHAgEWI2h0dHA6Ly9jZXJ0LnN0YXJ0 -Y29tLm9yZy9wb2xpY3kucGRmMDUGCCsGAQUFBwIBFilodHRwOi8vY2VydC5zdGFy -dGNvbS5vcmcvaW50ZXJtZWRpYXRlLnBkZjCB0AYIKwYBBQUHAgIwgcMwJxYgU3Rh -cnQgQ29tbWVyY2lhbCAoU3RhcnRDb20pIEx0ZC4wAwIBARqBl0xpbWl0ZWQgTGlh -YmlsaXR5LCByZWFkIHRoZSBzZWN0aW9uICpMZWdhbCBMaW1pdGF0aW9ucyogb2Yg -dGhlIFN0YXJ0Q29tIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFBvbGljeSBhdmFp -bGFibGUgYXQgaHR0cDovL2NlcnQuc3RhcnRjb20ub3JnL3BvbGljeS5wZGYwEQYJ -YIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilTdGFydENvbSBGcmVlIFNT -TCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQUFAAOCAgEAFmyZ -9GYMNPXQhV59CuzaEE44HF7fpiUFS5Eyweg78T3dRAlbB0mKKctmArexmvclmAk8 -jhvh3TaHK0u7aNM5Zj2gJsfyOZEdUauCe37Vzlrk4gNXcGmXCPleWKYK34wGmkUW -FjgKXlf2Ysd6AgXmvB618p70qSmD+LIU424oh0TDkBreOKk8rENNZEXO3SipXPJz -ewT4F+irsfMuXGRuczE6Eri8sxHkfY+BUZo7jYn0TZNmezwD7dOaHZrzZVD1oNB1 -ny+v8OqCQ5j4aZyJecRDjkZy42Q2Eq/3JR44iZB3fsNrarnDy0RLrHiQi+fHLB5L -EUTINFInzQpdn4XBidUaePKVEFMy3YCEZnXZtWgo+2EuvoSoOMCZEoalHmdkrQYu -L6lwhceWD3yJZfWOQ1QOq92lgDmUYMA0yZZwLKMS9R9Ie70cfmu3nZD0Ijuu+Pwq -yvqCUqDvr0tVk+vBtfAii6w0TiYiBKGHLHVKt+V9E9e4DGTANtLJL4YSjCMJwRuC -O3NJo2pXh5Tl1njFmUNj403gdy3hZZlyaQQaRwnmDwFWJPsfvw55qVguucQJAX6V -um0ABj6y6koQOdjQK/W/7HW/lwLFCRsI3FU34oH7N4RDYiDK51ZLZer+bMEkkySh -NOsF/5oirpt9P/FlUQqmMGqz9IgcgA38corog14= ------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=GeoTrust Primary Certification Authority O=GeoTrust Inc. -# Subject: CN=GeoTrust Primary Certification Authority O=GeoTrust Inc. -# Label: "GeoTrust Primary Certification Authority" -# Serial: 32798226551256963324313806436981982369 -# MD5 Fingerprint: 02:26:c3:01:5e:08:30:37:43:a9:d0:7d:cf:37:e6:bf -# SHA1 Fingerprint: 32:3c:11:8e:1b:f7:b8:b6:52:54:e2:e2:10:0d:d6:02:90:37:f0:96 -# SHA256 Fingerprint: 37:d5:10:06:c5:12:ea:ab:62:64:21:f1:ec:8c:92:01:3f:c5:f8:2a:e9:8e:e5:33:eb:46:19:b8:de:b4:d0:6c ------BEGIN CERTIFICATE----- -MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBY -MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMo -R2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEx -MjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgxCzAJBgNVBAYTAlVTMRYwFAYDVQQK -Ew1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQcmltYXJ5IENlcnRp -ZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC -AQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9 -AWbK7hWNb6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjA -ZIVcFU2Ix7e64HXprQU9nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE0 -7e9GceBrAqg1cmuXm2bgyxx5X9gaBGgeRwLmnWDiNpcB3841kt++Z8dtd1k7j53W -kBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGttm/81w7a4DSwDRp35+MI -mO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G -A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJ -KoZIhvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ1 -6CePbJC/kRYkRj5KTs4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl -4b7UVXGYNTq+k+qurUKykG/g/CFNNWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6K -oKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHaFloxt/m0cYASSJlyc1pZU8Fj -UjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG1riR/aYNKxoU -AT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk= ------END CERTIFICATE----- - -# Issuer: CN=thawte Primary Root CA O=thawte, Inc. OU=Certification Services Division/(c) 2006 thawte, Inc. - For authorized use only -# Subject: CN=thawte Primary Root CA O=thawte, Inc. OU=Certification Services Division/(c) 2006 thawte, Inc. - For authorized use only -# Label: "thawte Primary Root CA" -# Serial: 69529181992039203566298953787712940909 -# MD5 Fingerprint: 8c:ca:dc:0b:22:ce:f5:be:72:ac:41:1a:11:a8:d8:12 -# SHA1 Fingerprint: 91:c6:d6:ee:3e:8a:c8:63:84:e5:48:c2:99:29:5c:75:6c:81:7b:81 -# SHA256 Fingerprint: 8d:72:2f:81:a9:c1:13:c0:79:1d:f1:36:a2:96:6d:b2:6c:95:0a:97:1d:b4:6b:41:99:f4:ea:54:b7:8b:fb:9f ------BEGIN CERTIFICATE----- -MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCB -qTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf -Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw -MDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNV -BAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3MDAwMDAwWhcNMzYw -NzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5j -LjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYG -A1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl -IG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqG -SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCsoPD7gFnUnMekz52hWXMJEEUMDSxuaPFs -W0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ1CRfBsDMRJSUjQJib+ta -3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGcq/gcfomk -6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6 -Sk/KaAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94J -NqR32HuHUETVPm4pafs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBA -MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XP -r87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUFAAOCAQEAeRHAS7ORtvzw6WfU -DW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeEuzLlQRHAd9mz -YJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX -xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2 -/qxAeeWsEG89jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/ -LHbTY5xZ3Y+m4Q6gLkH3LpVHz7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7 -jVaMaA== ------END CERTIFICATE----- - -# Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G5 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2006 VeriSign, Inc. - For authorized use only -# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G5 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2006 VeriSign, Inc. - For authorized use only -# Label: "VeriSign Class 3 Public Primary Certification Authority - G5" -# Serial: 33037644167568058970164719475676101450 -# MD5 Fingerprint: cb:17:e4:31:67:3e:e2:09:fe:45:57:93:f3:0a:fa:1c -# SHA1 Fingerprint: 4e:b6:d5:78:49:9b:1c:cf:5f:58:1e:ad:56:be:3d:9b:67:44:a5:e5 -# SHA256 Fingerprint: 9a:cf:ab:7e:43:c8:d8:80:d0:6b:26:2a:94:de:ee:e4:b4:65:99:89:c3:d0:ca:f1:9b:af:64:05:e4:1a:b7:df ------BEGIN CERTIFICATE----- -MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCB -yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL -ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp -U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW -ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0 -aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCByjEL -MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW -ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2ln -biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp -U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y -aXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1 -nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbex -t0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIz -SdhDY2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQG -BO+QueQA5N06tRn/Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+ -rCpSx4/VBEnkjWNHiDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/ -NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E -BAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAH -BgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy -aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKv -MzEzMA0GCSqGSIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzE -p6B4Eq1iDkVwZMXnl2YtmAl+X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y -5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKEKQsTb47bDN0lAtukixlE0kF6BWlK -WE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiCKm0oHw0LxOXnGiYZ -4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vEZV8N -hnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq ------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=Network Solutions Certificate Authority O=Network Solutions L.L.C. -# Subject: CN=Network Solutions Certificate Authority O=Network Solutions L.L.C. -# Label: "Network Solutions Certificate Authority" -# Serial: 116697915152937497490437556386812487904 -# MD5 Fingerprint: d3:f3:a6:16:c0:fa:6b:1d:59:b1:2d:96:4d:0e:11:2e -# SHA1 Fingerprint: 74:f8:a3:c3:ef:e7:b3:90:06:4b:83:90:3c:21:64:60:20:e5:df:ce -# SHA256 Fingerprint: 15:f0:ba:00:a3:ac:7a:f3:ac:88:4c:07:2b:10:11:a0:77:bd:77:c0:97:f4:01:64:b2:f8:59:8a:bd:83:86:0c ------BEGIN CERTIFICATE----- -MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBi -MQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu -MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3Jp -dHkwHhcNMDYxMjAxMDAwMDAwWhcNMjkxMjMxMjM1OTU5WjBiMQswCQYDVQQGEwJV -UzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYDVQQDEydO -ZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0GCSqG -SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkvH6SMG3G2I4rC7xGzuAnlt7e+foS0zwz -c7MEL7xxjOWftiJgPl9dzgn/ggwbmlFQGiaJ3dVhXRncEg8tCqJDXRfQNJIg6nPP -OCwGJgl6cvf6UDL4wpPTaaIjzkGxzOTVHzbRijr4jGPiFFlp7Q3Tf2vouAPlT2rl -mGNpSAW+Lv8ztumXWWn4Zxmuk2GWRBXTcrA/vGp97Eh/jcOrqnErU2lBUzS1sLnF -BgrEsEX1QV1uiUV7PTsmjHTC5dLRfbIR1PtYMiKagMnc/Qzpf14Dl847ABSHJ3A4 -qY5usyd2mFHgBeMhqxrVhSI8KbWaFsWAqPS7azCPL0YCorEMIuDTAgMBAAGjgZcw -gZQwHQYDVR0OBBYEFCEwyfsA106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB/wQEAwIB -BjAPBgNVHRMBAf8EBTADAQH/MFIGA1UdHwRLMEkwR6BFoEOGQWh0dHA6Ly9jcmwu -bmV0c29sc3NsLmNvbS9OZXR3b3JrU29sdXRpb25zQ2VydGlmaWNhdGVBdXRob3Jp -dHkuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQC7rkvnt1frf6ott3NHhWrB5KUd5Oc8 -6fRZZXe1eltajSU24HqXLjjAV2CDmAaDn7l2em5Q4LqILPxFzBiwmZVRDuwduIj/ -h1AcgsLj4DKAv6ALR8jDMe+ZZzKATxcheQxpXN5eNK4CtSbqUN9/GGUsyfJj4akH -/nxxH2szJGoeBfcFaMBqEssuXmHLrijTfsK0ZpEmXzwuJF/LWA/rKOyvEZbz3Htv -wKeI8lN3s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHN -pGxlaKFJdlxDydi8NmdspZS11My5vWo1ViHe2MPr+8ukYEywVaCge1ey ------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=TC TrustCenter Class 2 CA II O=TC TrustCenter GmbH OU=TC TrustCenter Class 2 CA -# Subject: CN=TC TrustCenter Class 2 CA II O=TC TrustCenter GmbH OU=TC TrustCenter Class 2 CA -# Label: "TC TrustCenter Class 2 CA II" -# Serial: 941389028203453866782103406992443 -# MD5 Fingerprint: ce:78:33:5c:59:78:01:6e:18:ea:b9:36:a0:b9:2e:23 -# SHA1 Fingerprint: ae:50:83:ed:7c:f4:5c:bc:8f:61:c6:21:fe:68:5d:79:42:21:15:6e -# SHA256 Fingerprint: e6:b8:f8:76:64:85:f8:07:ae:7f:8d:ac:16:70:46:1f:07:c0:a1:3e:ef:3a:1f:f7:17:53:8d:7a:ba:d3:91:b4 ------BEGIN CERTIFICATE----- -MIIEqjCCA5KgAwIBAgIOLmoAAQACH9dSISwRXDswDQYJKoZIhvcNAQEFBQAwdjEL -MAkGA1UEBhMCREUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNV -BAsTGVRDIFRydXN0Q2VudGVyIENsYXNzIDIgQ0ExJTAjBgNVBAMTHFRDIFRydXN0 -Q2VudGVyIENsYXNzIDIgQ0EgSUkwHhcNMDYwMTEyMTQzODQzWhcNMjUxMjMxMjI1 -OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1c3RDZW50ZXIgR21i -SDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQTElMCMGA1UEAxMc -VEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQAD -ggEPADCCAQoCggEBAKuAh5uO8MN8h9foJIIRszzdQ2Lu+MNF2ujhoF/RKrLqk2jf -tMjWQ+nEdVl//OEd+DFwIxuInie5e/060smp6RQvkL4DUsFJzfb95AhmC1eKokKg -uNV/aVyQMrKXDcpK3EY+AlWJU+MaWss2xgdW94zPEfRMuzBwBJWl9jmM/XOBCH2J -XjIeIqkiRUuwZi4wzJ9l/fzLganx4Duvo4bRierERXlQXa7pIXSSTYtZgo+U4+lK -8edJsBTj9WLL1XK9H7nSn6DNqPoByNkN39r8R52zyFTfSUrxIan+GE7uSNQZu+99 -5OKdy1u2bv/jzVrndIIFuoAlOMvkaZ6vQaoahPUCAwEAAaOCATQwggEwMA8GA1Ud -EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTjq1RMgKHbVkO3 -kUrL84J6E1wIqzCB7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRy -dXN0Y2VudGVyLmRlL2NybC92Mi90Y19jbGFzc18yX2NhX0lJLmNybIaBn2xkYXA6 -Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBUcnVzdENlbnRlciUyMENsYXNz -JTIwMiUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21iSCxPVT1yb290 -Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u -TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEAjNfffu4bgBCzg/XbEeprS6iS -GNn3Bzn1LL4GdXpoUxUc6krtXvwjshOg0wn/9vYua0Fxec3ibf2uWWuFHbhOIprt -ZjluS5TmVfwLG4t3wVMTZonZKNaL80VKY7f9ewthXbhtvsPcW3nS7Yblok2+XnR8 -au0WOB9/WIFaGusyiC2y8zl3gK9etmF1KdsjTYjKUCjLhdLTEKJZbtOTVAB6okaV -hgWcqRmY5TFyDADiZ9lA4CQze28suVyrZZ0srHbqNZn1l7kPJOzHdiEoZa5X6AeI -dUpWoNIFOqTmjZKILPPy4cHGYdtBxceb9w4aUUXCYWvcZCcXjFq32nQozZfkvQ== ------END CERTIFICATE----- - -# Issuer: CN=TC TrustCenter Class 3 CA II O=TC TrustCenter GmbH OU=TC TrustCenter Class 3 CA -# Subject: CN=TC TrustCenter Class 3 CA II O=TC TrustCenter GmbH OU=TC TrustCenter Class 3 CA -# Label: "TC TrustCenter Class 3 CA II" -# Serial: 1506523511417715638772220530020799 -# MD5 Fingerprint: 56:5f:aa:80:61:12:17:f6:67:21:e6:2b:6d:61:56:8e -# SHA1 Fingerprint: 80:25:ef:f4:6e:70:c8:d4:72:24:65:84:fe:40:3b:8a:8d:6a:db:f5 -# SHA256 Fingerprint: 8d:a0:84:fc:f9:9c:e0:77:22:f8:9b:32:05:93:98:06:fa:5c:b8:11:e1:c8:13:f6:a1:08:c7:d3:36:b3:40:8e ------BEGIN CERTIFICATE----- -MIIEqjCCA5KgAwIBAgIOSkcAAQAC5aBd1j8AUb8wDQYJKoZIhvcNAQEFBQAwdjEL -MAkGA1UEBhMCREUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNV -BAsTGVRDIFRydXN0Q2VudGVyIENsYXNzIDMgQ0ExJTAjBgNVBAMTHFRDIFRydXN0 -Q2VudGVyIENsYXNzIDMgQ0EgSUkwHhcNMDYwMTEyMTQ0MTU3WhcNMjUxMjMxMjI1 -OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1c3RDZW50ZXIgR21i -SDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQTElMCMGA1UEAxMc -VEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQAD -ggEPADCCAQoCggEBALTgu1G7OVyLBMVMeRwjhjEQY0NVJz/GRcekPewJDRoeIMJW -Ht4bNwcwIi9v8Qbxq63WyKthoy9DxLCyLfzDlml7forkzMA5EpBCYMnMNWju2l+Q -Vl/NHE1bWEnrDgFPZPosPIlY2C8u4rBo6SI7dYnWRBpl8huXJh0obazovVkdKyT2 -1oQDZogkAHhg8fir/gKya/si+zXmFtGt9i4S5Po1auUZuV3bOx4a+9P/FRQI2Alq -ukWdFHlgfa9Aigdzs5OW03Q0jTo3Kd5c7PXuLjHCINy+8U9/I1LZW+Jk2ZyqBwi1 -Rb3R0DHBq1SfqdLDYmAD8bs5SpJKPQq5ncWg/jcCAwEAAaOCATQwggEwMA8GA1Ud -EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTUovyfs8PYA9NX -XAek0CSnwPIA1DCB7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRy -dXN0Y2VudGVyLmRlL2NybC92Mi90Y19jbGFzc18zX2NhX0lJLmNybIaBn2xkYXA6 -Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBUcnVzdENlbnRlciUyMENsYXNz -JTIwMyUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21iSCxPVT1yb290 -Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u -TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEANmDkcPcGIEPZIxpC8vijsrlN -irTzwppVMXzEO2eatN9NDoqTSheLG43KieHPOh6sHfGcMrSOWXaiQYUlN6AT0PV8 -TtXqluJucsG7Kv5sbviRmEb8yRtXW+rIGjs/sFGYPAfaLFkB2otE6OF0/ado3VS6 -g0bsyEa1+K+XwDsJHI/OcpY9M1ZwvJbL2NV9IJqDnxrcOfHFcqMRA/07QlIp2+gB -95tejNaNhk4Z+rwcvsUhpYeeeC422wlxo3I0+GzjBgnyXlal092Y+tTmBvTwtiBj -S+opvaqCZh77gaqnN60TGOaSw4HBM7uIHqHn4rS9MWwOUT1v+5ZWgOI2F9Hc5A== ------END CERTIFICATE----- - -# Issuer: CN=TC TrustCenter Universal CA I O=TC TrustCenter GmbH OU=TC TrustCenter Universal CA -# Subject: CN=TC TrustCenter Universal CA I O=TC TrustCenter GmbH OU=TC TrustCenter Universal CA -# Label: "TC TrustCenter Universal CA I" -# Serial: 601024842042189035295619584734726 -# MD5 Fingerprint: 45:e1:a5:72:c5:a9:36:64:40:9e:f5:e4:58:84:67:8c -# SHA1 Fingerprint: 6b:2f:34:ad:89:58:be:62:fd:b0:6b:5c:ce:bb:9d:d9:4f:4e:39:f3 -# SHA256 Fingerprint: eb:f3:c0:2a:87:89:b1:fb:7d:51:19:95:d6:63:b7:29:06:d9:13:ce:0d:5e:10:56:8a:8a:77:e2:58:61:67:e7 ------BEGIN CERTIFICATE----- -MIID3TCCAsWgAwIBAgIOHaIAAQAC7LdggHiNtgYwDQYJKoZIhvcNAQEFBQAweTEL -MAkGA1UEBhMCREUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxJDAiBgNV -BAsTG1RDIFRydXN0Q2VudGVyIFVuaXZlcnNhbCBDQTEmMCQGA1UEAxMdVEMgVHJ1 -c3RDZW50ZXIgVW5pdmVyc2FsIENBIEkwHhcNMDYwMzIyMTU1NDI4WhcNMjUxMjMx -MjI1OTU5WjB5MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1c3RDZW50ZXIg -R21iSDEkMCIGA1UECxMbVEMgVHJ1c3RDZW50ZXIgVW5pdmVyc2FsIENBMSYwJAYD -VQQDEx1UQyBUcnVzdENlbnRlciBVbml2ZXJzYWwgQ0EgSTCCASIwDQYJKoZIhvcN -AQEBBQADggEPADCCAQoCggEBAKR3I5ZEr5D0MacQ9CaHnPM42Q9e3s9B6DGtxnSR -JJZ4Hgmgm5qVSkr1YnwCqMqs+1oEdjneX/H5s7/zA1hV0qq34wQi0fiU2iIIAI3T -fCZdzHd55yx4Oagmcw6iXSVphU9VDprvxrlE4Vc93x9UIuVvZaozhDrzznq+VZeu -jRIPFDPiUHDDSYcTvFHe15gSWu86gzOSBnWLknwSaHtwag+1m7Z3W0hZneTvWq3z -wZ7U10VOylY0Ibw+F1tvdwxIAUMpsN0/lm7mlaoMwCC2/T42J5zjXM9OgdwZu5GQ -fezmlwQek8wiSdeXhrYTCjxDI3d+8NzmzSQfO4ObNDqDNOMCAwEAAaNjMGEwHwYD -VR0jBBgwFoAUkqR1LKSevoFE63n8isWVpesQdXMwDwYDVR0TAQH/BAUwAwEB/zAO -BgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFJKkdSyknr6BROt5/IrFlaXrEHVzMA0G -CSqGSIb3DQEBBQUAA4IBAQAo0uCG1eb4e/CX3CJrO5UUVg8RMKWaTzqwOuAGy2X1 -7caXJ/4l8lfmXpWMPmRgFVp/Lw0BxbFg/UU1z/CyvwbZ71q+s2IhtNerNXxTPqYn -8aEt2hojnczd7Dwtnic0XQ/CNnm8yUpiLe1r2X1BQ3y2qsrtYbE3ghUJGooWMNjs -ydZHcnhLEEYUjl8Or+zHL6sQ17bxbuyGssLoDZJz3KL0Dzq/YSMQiZxIQG5wALPT -ujdEWBF6AmqI8Dc08BnprNRlc/ZpjGSUOnmFKbAWKwyCPwacx/0QK54PLLae4xW/ -2TYcuiUaUj0a7CIMHOCkoj3w6DnPgcB77V0fb8XQC9eY ------END CERTIFICATE----- - -# Issuer: CN=Cybertrust Global Root O=Cybertrust, Inc -# Subject: CN=Cybertrust Global Root O=Cybertrust, Inc -# Label: "Cybertrust Global Root" -# Serial: 4835703278459682877484360 -# MD5 Fingerprint: 72:e4:4a:87:e3:69:40:80:77:ea:bc:e3:f4:ff:f0:e1 -# SHA1 Fingerprint: 5f:43:e5:b1:bf:f8:78:8c:ac:1c:c7:ca:4a:9a:c6:22:2b:cc:34:c6 -# SHA256 Fingerprint: 96:0a:df:00:63:e9:63:56:75:0c:29:65:dd:0a:08:67:da:0b:9c:bd:6e:77:71:4a:ea:fb:23:49:ab:39:3d:a3 ------BEGIN CERTIFICATE----- -MIIDoTCCAomgAwIBAgILBAAAAAABD4WqLUgwDQYJKoZIhvcNAQEFBQAwOzEYMBYG -A1UEChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2Jh -bCBSb290MB4XDTA2MTIxNTA4MDAwMFoXDTIxMTIxNTA4MDAwMFowOzEYMBYGA1UE -ChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2JhbCBS -b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+Mi8vRRQZhP/8NN5 -7CPytxrHjoXxEnOmGaoQ25yiZXRadz5RfVb23CO21O1fWLE3TdVJDm71aofW0ozS -J8bi/zafmGWgE07GKmSb1ZASzxQG9Dvj1Ci+6A74q05IlG2OlTEQXO2iLb3VOm2y -HLtgwEZLAfVJrn5GitB0jaEMAs7u/OePuGtm839EAL9mJRQr3RAwHQeWP032a7iP -t3sMpTjr3kfb1V05/Iin89cqdPHoWqI7n1C6poxFNcJQZZXcY4Lv3b93TZxiyWNz -FtApD0mpSPCzqrdsxacwOUBdrsTiXSZT8M4cIwhhqJQZugRiQOwfOHB3EgZxpzAY -XSUnpQIDAQABo4GlMIGiMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/ -MB0GA1UdDgQWBBS2CHsNesysIEyGVjJez6tuhS1wVzA/BgNVHR8EODA2MDSgMqAw -hi5odHRwOi8vd3d3Mi5wdWJsaWMtdHJ1c3QuY29tL2NybC9jdC9jdHJvb3QuY3Js -MB8GA1UdIwQYMBaAFLYIew16zKwgTIZWMl7Pq26FLXBXMA0GCSqGSIb3DQEBBQUA -A4IBAQBW7wojoFROlZfJ+InaRcHUowAl9B8Tq7ejhVhpwjCt2BWKLePJzYFa+HMj -Wqd8BfP9IjsO0QbE2zZMcwSO5bAi5MXzLqXZI+O4Tkogp24CJJ8iYGd7ix1yCcUx -XOl5n4BHPa2hCwcUPUf/A2kaDAtE52Mlp3+yybh2hO0j9n0Hq0V+09+zv+mKts2o -omcrUtW3ZfA5TGOgkXmTUg9U3YO7n9GPp1Nzw8v/MOx8BLjYRB+TX3EJIrduPuoc -A06dGiBh+4E37F78CkWr1+cXVdCg6mCbpvbjjFspwgZgFJ0tl0ypkxWdYcQBX0jW -WL1WMRJOEcgh4LMRkWXbtKaIOM5V ------END CERTIFICATE----- - -# Issuer: CN=GeoTrust Primary Certification Authority - G3 O=GeoTrust Inc. OU=(c) 2008 GeoTrust Inc. - For authorized use only -# Subject: CN=GeoTrust Primary Certification Authority - G3 O=GeoTrust Inc. OU=(c) 2008 GeoTrust Inc. - For authorized use only -# Label: "GeoTrust Primary Certification Authority - G3" -# Serial: 28809105769928564313984085209975885599 -# MD5 Fingerprint: b5:e8:34:36:c9:10:44:58:48:70:6d:2e:83:d4:b8:05 -# SHA1 Fingerprint: 03:9e:ed:b8:0b:e7:a0:3c:69:53:89:3b:20:d2:d9:32:3a:4c:2a:fd -# SHA256 Fingerprint: b4:78:b8:12:25:0d:f8:78:63:5c:2a:a7:ec:7d:15:5e:aa:62:5e:e8:29:16:e2:cd:29:43:61:88:6c:d1:fb:d4 ------BEGIN CERTIFICATE----- -MIID/jCCAuagAwIBAgIQFaxulBmyeUtB9iepwxgPHzANBgkqhkiG9w0BAQsFADCB -mDELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsT -MChjKSAyMDA4IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25s -eTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhv -cml0eSAtIEczMB4XDTA4MDQwMjAwMDAwMFoXDTM3MTIwMTIzNTk1OVowgZgxCzAJ -BgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykg -MjAwOCBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0 -BgNVBAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg -LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANziXmJYHTNXOTIz -+uvLh4yn1ErdBojqZI4xmKU4kB6Yzy5jK/BGvESyiaHAKAxJcCGVn2TAppMSAmUm -hsalifD614SgcK9PGpc/BkTVyetyEH3kMSj7HGHmKAdEc5IiaacDiGydY8hS2pgn -5whMcD60yRLBxWeDXTPzAxHsatBT4tG6NmCUgLthY2xbF37fQJQeqw3CIShwiP/W -JmxsYAQlTlV+fe+/lEjetx3dcI0FX4ilm/LC7urRQEFtYjgdVgbFA0dRIBn8exAL -DmKudlW/X3e+PkkBUz2YJQN2JFodtNuJ6nnltrM7P7pMKEF/BqxqjsHQ9gUdfeZC -huOl1UcCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw -HQYDVR0OBBYEFMR5yo6hTgMdHNxr2zFblD4/MH8tMA0GCSqGSIb3DQEBCwUAA4IB -AQAtxRPPVoB7eni9n64smefv2t+UXglpp+duaIy9cr5HqQ6XErhK8WTTOd8lNNTB -zU6B8A8ExCSzNJbGpqow32hhc9f5joWJ7w5elShKKiePEI4ufIbEAp7aDHdlDkQN -kv39sxY2+hENHYwOB4lqKVb3cvTdFZx3NWZXqxNT2I7BQMXXExZacse3aQHEerGD -AWh9jUGhlBjBJVz88P6DAod8DQ3PLghcSkANPuyBYeYk28rgDi0Hsj5W3I31QYUH -SJsMC8tJP33st/3LjWeJGqvtux6jAAgIFyqCXDFdRootD4abdNlF+9RAsXqqaC2G -spki4cErx5z481+oghLrGREt ------END CERTIFICATE----- - -# Issuer: CN=thawte Primary Root CA - G2 O=thawte, Inc. OU=(c) 2007 thawte, Inc. - For authorized use only -# Subject: CN=thawte Primary Root CA - G2 O=thawte, Inc. OU=(c) 2007 thawte, Inc. - For authorized use only -# Label: "thawte Primary Root CA - G2" -# Serial: 71758320672825410020661621085256472406 -# MD5 Fingerprint: 74:9d:ea:60:24:c4:fd:22:53:3e:cc:3a:72:d9:29:4f -# SHA1 Fingerprint: aa:db:bc:22:23:8f:c4:01:a1:27:bb:38:dd:f4:1d:db:08:9e:f0:12 -# SHA256 Fingerprint: a4:31:0d:50:af:18:a6:44:71:90:37:2a:86:af:af:8b:95:1f:fb:43:1d:83:7f:1e:56:88:b4:59:71:ed:15:57 ------BEGIN CERTIFICATE----- -MIICiDCCAg2gAwIBAgIQNfwmXNmET8k9Jj1Xm67XVjAKBggqhkjOPQQDAzCBhDEL -MAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjE4MDYGA1UECxMvKGMp -IDIwMDcgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAi -BgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMjAeFw0wNzExMDUwMDAw -MDBaFw0zODAxMTgyMzU5NTlaMIGEMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhh -d3RlLCBJbmMuMTgwNgYDVQQLEy8oYykgMjAwNyB0aGF3dGUsIEluYy4gLSBGb3Ig -YXV0aG9yaXplZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9v -dCBDQSAtIEcyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEotWcgnuVnfFSeIf+iha/ -BebfowJPDQfGAFG6DAJSLSKkQjnE/o/qycG+1E3/n3qe4rF8mq2nhglzh9HnmuN6 -papu+7qzcMBniKI11KOasf2twu8x+qi58/sIxpHR+ymVo0IwQDAPBgNVHRMBAf8E -BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUmtgAMADna3+FGO6Lts6K -DPgR4bswCgYIKoZIzj0EAwMDaQAwZgIxAN344FdHW6fmCsO99YCKlzUNG4k8VIZ3 -KMqh9HneteY4sPBlcIx/AlTCv//YoT7ZzwIxAMSNlPzcU9LcnXgWHxUzI1NS41ox -XZ3Krr0TKUQNJ1uo52icEvdYPy5yAlejj6EULg== ------END CERTIFICATE----- - -# Issuer: CN=thawte Primary Root CA - G3 O=thawte, Inc. OU=Certification Services Division/(c) 2008 thawte, Inc. - For authorized use only -# Subject: CN=thawte Primary Root CA - G3 O=thawte, Inc. OU=Certification Services Division/(c) 2008 thawte, Inc. - For authorized use only -# Label: "thawte Primary Root CA - G3" -# Serial: 127614157056681299805556476275995414779 -# MD5 Fingerprint: fb:1b:5d:43:8a:94:cd:44:c6:76:f2:43:4b:47:e7:31 -# SHA1 Fingerprint: f1:8b:53:8d:1b:e9:03:b6:a6:f0:56:43:5b:17:15:89:ca:f3:6b:f2 -# SHA256 Fingerprint: 4b:03:f4:58:07:ad:70:f2:1b:fc:2c:ae:71:c9:fd:e4:60:4c:06:4c:f5:ff:b6:86:ba:e5:db:aa:d7:fd:d3:4c ------BEGIN CERTIFICATE----- -MIIEKjCCAxKgAwIBAgIQYAGXt0an6rS0mtZLL/eQ+zANBgkqhkiG9w0BAQsFADCB -rjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf -Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw -MDggdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAiBgNV -BAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMzAeFw0wODA0MDIwMDAwMDBa -Fw0zNzEyMDEyMzU5NTlaMIGuMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhhd3Rl -LCBJbmMuMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9u -MTgwNgYDVQQLEy8oYykgMjAwOCB0aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXpl -ZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAtIEcz -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsr8nLPvb2FvdeHsbnndm -gcs+vHyu86YnmjSjaDFxODNi5PNxZnmxqWWjpYvVj2AtP0LMqmsywCPLLEHd5N/8 -YZzic7IilRFDGF/Eth9XbAoFWCLINkw6fKXRz4aviKdEAhN0cXMKQlkC+BsUa0Lf -b1+6a4KinVvnSr0eAXLbS3ToO39/fR8EtCab4LRarEc9VbjXsCZSKAExQGbY2SS9 -9irY7CFJXJv2eul/VTV+lmuNk5Mny5K76qxAwJ/C+IDPXfRa3M50hqY+bAtTyr2S -zhkGcuYMXDhpxwTWvGzOW/b3aJzcJRVIiKHpqfiYnODz1TEoYRFsZ5aNOZnLwkUk -OQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNV -HQ4EFgQUrWyqlGCc7eT/+j4KdCtjA/e2Wb8wDQYJKoZIhvcNAQELBQADggEBABpA -2JVlrAmSicY59BDlqQ5mU1143vokkbvnRFHfxhY0Cu9qRFHqKweKA3rD6z8KLFIW -oCtDuSWQP3CpMyVtRRooOyfPqsMpQhvfO0zAMzRbQYi/aytlryjvsvXDqmbOe1bu -t8jLZ8HJnBoYuMTDSQPxYA5QzUbF83d597YV4Djbxy8ooAw/dyZ02SUS2jHaGh7c -KUGRIjxpp7sC8rZcJwOJ9Abqm+RyguOhCcHpABnTPtRwa7pxpqpYrvS76Wy274fM -m7v/OeZWYdMKp8RcTGB7BXcmer/YB1IsYvdwY9k5vG8cwnncdimvzsUsZAReiDZu -MdRAGmI0Nj81Aa6sY6A= ------END CERTIFICATE----- - -# Issuer: CN=GeoTrust Primary Certification Authority - G2 O=GeoTrust Inc. OU=(c) 2007 GeoTrust Inc. - For authorized use only -# Subject: CN=GeoTrust Primary Certification Authority - G2 O=GeoTrust Inc. OU=(c) 2007 GeoTrust Inc. - For authorized use only -# Label: "GeoTrust Primary Certification Authority - G2" -# Serial: 80682863203381065782177908751794619243 -# MD5 Fingerprint: 01:5e:d8:6b:bd:6f:3d:8e:a1:31:f8:12:e0:98:73:6a -# SHA1 Fingerprint: 8d:17:84:d5:37:f3:03:7d:ec:70:fe:57:8b:51:9a:99:e6:10:d7:b0 -# SHA256 Fingerprint: 5e:db:7a:c4:3b:82:a0:6a:87:61:e8:d7:be:49:79:eb:f2:61:1f:7d:d7:9b:f9:1c:1c:6b:56:6a:21:9e:d7:66 ------BEGIN CERTIFICATE----- -MIICrjCCAjWgAwIBAgIQPLL0SAoA4v7rJDteYD7DazAKBggqhkjOPQQDAzCBmDEL -MAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChj -KSAyMDA3IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2 -MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0 -eSAtIEcyMB4XDTA3MTEwNTAwMDAwMFoXDTM4MDExODIzNTk1OVowgZgxCzAJBgNV -BAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykgMjAw -NyBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNV -BAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBH -MjB2MBAGByqGSM49AgEGBSuBBAAiA2IABBWx6P0DFUPlrOuHNxFi79KDNlJ9RVcL -So17VDs6bl8VAsBQps8lL33KSLjHUGMcKiEIfJo22Av+0SbFWDEwKCXzXV2juLal -tJLtbCyf691DiaI8S0iRHVDsJt/WYC69IaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO -BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBVfNVdRVfslsq0DafwBo/q+EVXVMAoG -CCqGSM49BAMDA2cAMGQCMGSWWaboCd6LuvpaiIjwH5HTRqjySkwCY/tsXzjbLkGT -qQ7mndwxHLKgpxgceeHHNgIwOlavmnRs9vuD4DPTCF+hnMJbn0bWtsuRBmOiBucz -rD6ogRLQy7rQkgu2npaqBA+K ------END CERTIFICATE----- - -# Issuer: CN=VeriSign Universal Root Certification Authority O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2008 VeriSign, Inc. - For authorized use only -# Subject: CN=VeriSign Universal Root Certification Authority O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2008 VeriSign, Inc. - For authorized use only -# Label: "VeriSign Universal Root Certification Authority" -# Serial: 85209574734084581917763752644031726877 -# MD5 Fingerprint: 8e:ad:b5:01:aa:4d:81:e4:8c:1d:d1:e1:14:00:95:19 -# SHA1 Fingerprint: 36:79:ca:35:66:87:72:30:4d:30:a5:fb:87:3b:0f:a7:7b:b7:0d:54 -# SHA256 Fingerprint: 23:99:56:11:27:a5:71:25:de:8c:ef:ea:61:0d:df:2f:a0:78:b5:c8:06:7f:4e:82:82:90:bf:b8:60:e8:4b:3c ------BEGIN CERTIFICATE----- -MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCB -vTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL -ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJp -U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MTgwNgYDVQQDEy9W -ZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe -Fw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJVUzEX -MBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0 -IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9y -IGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNh -bCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF -AAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj1mCOkdeQmIN65lgZOIzF -9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGPMiJhgsWH -H26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+H -LL729fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN -/BMReYTtXlT2NJ8IAfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPT -rJ9VAMf2CGqUuV/c4DPxhGD5WycRtPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1Ud -EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0GCCsGAQUFBwEMBGEwX6FdoFsw -WTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2Oa8PPgGrUSBgs -exkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud -DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4 -sAPmLGd75JR3Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+ -seQxIcaBlVZaDrHC1LGmWazxY8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz -4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTxP/jgdFcrGJ2BtMQo2pSXpXDrrB2+ -BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+PwGZsY6rp2aQW9IHR -lRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4mJO3 -7M2CYfE45k+XmCpajQ== ------END CERTIFICATE----- - -# Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G4 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2007 VeriSign, Inc. - For authorized use only -# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G4 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2007 VeriSign, Inc. - For authorized use only -# Label: "VeriSign Class 3 Public Primary Certification Authority - G4" -# Serial: 63143484348153506665311985501458640051 -# MD5 Fingerprint: 3a:52:e1:e7:fd:6f:3a:e3:6f:f3:6f:99:1b:f9:22:41 -# SHA1 Fingerprint: 22:d5:d8:df:8f:02:31:d1:8d:f7:9d:b7:cf:8a:2d:64:c9:3f:6c:3a -# SHA256 Fingerprint: 69:dd:d7:ea:90:bb:57:c9:3e:13:5d:c8:5e:a6:fc:d5:48:0b:60:32:39:bd:c4:54:fc:75:8b:2a:26:cf:7f:79 ------BEGIN CERTIFICATE----- -MIIDhDCCAwqgAwIBAgIQL4D+I4wOIg9IZxIokYesszAKBggqhkjOPQQDAzCByjEL -MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW -ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2ln -biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp -U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y -aXR5IC0gRzQwHhcNMDcxMTA1MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCByjELMAkG -A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJp -U2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwg -SW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2ln -biBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5 -IC0gRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASnVnp8Utpkmw4tXNherJI9/gHm -GUo9FANL+mAnINmDiWn6VMaaGF5VKmTeBvaNSjutEDxlPZCIBIngMGGzrl0Bp3ve -fLK+ymVhAIau2o970ImtTR1ZmkGxvEeA3J5iw/mjgbIwga8wDwYDVR0TAQH/BAUw -AwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJ -aW1hZ2UvZ2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYj -aHR0cDovL2xvZ28udmVyaXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFLMW -kf3upm7ktS5Jj4d4gYDs5bG1MAoGCCqGSM49BAMDA2gAMGUCMGYhDBgmYFo4e1ZC -4Kf8NoRRkSAsdk1DPcQdhCPQrNZ8NQbOzWm9kA3bbEhCHQ6qQgIxAJw9SDkjOVga -FRJZap7v1VmyHVIsmXHNxynfGyphe3HR3vPA5Q06Sqotp9iGKt0uEA== ------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=TC TrustCenter Universal CA III O=TC TrustCenter GmbH OU=TC TrustCenter Universal CA -# Subject: CN=TC TrustCenter Universal CA III O=TC TrustCenter GmbH OU=TC TrustCenter Universal CA -# Label: "TC TrustCenter Universal CA III" -# Serial: 2010889993983507346460533407902964 -# MD5 Fingerprint: 9f:dd:db:ab:ff:8e:ff:45:21:5f:f0:6c:9d:8f:fe:2b -# SHA1 Fingerprint: 96:56:cd:7b:57:96:98:95:d0:e1:41:46:68:06:fb:b8:c6:11:06:87 -# SHA256 Fingerprint: 30:9b:4a:87:f6:ca:56:c9:31:69:aa:a9:9c:6d:98:88:54:d7:89:2b:d5:43:7e:2d:07:b2:9c:be:da:55:d3:5d ------BEGIN CERTIFICATE----- -MIID4TCCAsmgAwIBAgIOYyUAAQACFI0zFQLkbPQwDQYJKoZIhvcNAQEFBQAwezEL -MAkGA1UEBhMCREUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxJDAiBgNV -BAsTG1RDIFRydXN0Q2VudGVyIFVuaXZlcnNhbCBDQTEoMCYGA1UEAxMfVEMgVHJ1 -c3RDZW50ZXIgVW5pdmVyc2FsIENBIElJSTAeFw0wOTA5MDkwODE1MjdaFw0yOTEy -MzEyMzU5NTlaMHsxCzAJBgNVBAYTAkRFMRwwGgYDVQQKExNUQyBUcnVzdENlbnRl -ciBHbWJIMSQwIgYDVQQLExtUQyBUcnVzdENlbnRlciBVbml2ZXJzYWwgQ0ExKDAm -BgNVBAMTH1RDIFRydXN0Q2VudGVyIFVuaXZlcnNhbCBDQSBJSUkwggEiMA0GCSqG -SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDC2pxisLlxErALyBpXsq6DFJmzNEubkKLF -5+cvAqBNLaT6hdqbJYUtQCggbergvbFIgyIpRJ9Og+41URNzdNW88jBmlFPAQDYv -DIRlzg9uwliT6CwLOunBjvvya8o84pxOjuT5fdMnnxvVZ3iHLX8LR7PH6MlIfK8v -zArZQe+f/prhsq75U7Xl6UafYOPfjdN/+5Z+s7Vy+EutCHnNaYlAJ/Uqwa1D7KRT -yGG299J5KmcYdkhtWyUB0SbFt1dpIxVbYYqt8Bst2a9c8SaQaanVDED1M4BDj5yj -dipFtK+/fz6HP3bFzSreIMUWWMv5G/UPyw0RUmS40nZid4PxWJ//AgMBAAGjYzBh -MB8GA1UdIwQYMBaAFFbn4VslQ4Dg9ozhcbyO5YAvxEjiMA8GA1UdEwEB/wQFMAMB -Af8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRW5+FbJUOA4PaM4XG8juWAL8RI -4jANBgkqhkiG9w0BAQUFAAOCAQEAg8ev6n9NCjw5sWi+e22JLumzCecYV42Fmhfz -dkJQEw/HkG8zrcVJYCtsSVgZ1OK+t7+rSbyUyKu+KGwWaODIl0YgoGhnYIg5IFHY -aAERzqf2EQf27OysGh+yZm5WZ2B6dF7AbZc2rrUNXWZzwCUyRdhKBgePxLcHsU0G -DeGl6/R1yrqc0L2z0zIkTO5+4nYES0lT2PLpVDP85XEfPRRclkvxOvIAu2y0+pZV -CIgJwcyRGSmwIC3/yzikQOEXvnlhgP8HA4ZMTnsGnxGGjYnuJ8Tb4rwZjgvDwxPH -LQNjO9Po5KIqwoIIlBZU8O8fJ5AluA0OKBtHd0e9HKgl8ZS0Zg== ------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=StartCom Certification Authority O=StartCom Ltd. OU=Secure Digital Certificate Signing -# Subject: CN=StartCom Certification Authority O=StartCom Ltd. OU=Secure Digital Certificate Signing -# Label: "StartCom Certification Authority" -# Serial: 45 -# MD5 Fingerprint: c9:3b:0d:84:41:fc:a4:76:79:23:08:57:de:10:19:16 -# SHA1 Fingerprint: a3:f1:33:3f:e2:42:bf:cf:c5:d1:4e:8f:39:42:98:40:68:10:d1:a0 -# SHA256 Fingerprint: e1:78:90:ee:09:a3:fb:f4:f4:8b:9c:41:4a:17:d6:37:b7:a5:06:47:e9:bc:75:23:22:72:7f:cc:17:42:a9:11 ------BEGIN CERTIFICATE----- -MIIHhzCCBW+gAwIBAgIBLTANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQGEwJJTDEW -MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg -Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh -dGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0NjM3WhcNMzYwOTE3MTk0NjM2WjB9 -MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMi -U2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3Rh -cnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA -A4ICDwAwggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZk -pMyONvg45iPwbm2xPN1yo4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rf -OQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/C -Ji/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/deMotHweXMAEtcnn6RtYT -Kqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt2PZE4XNi -HzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMM -Av+Z6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w -+2OqqGwaVLRcJXrJosmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+ -Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3 -Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVcUjyJthkqcwEKDwOzEmDyei+B -26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT37uMdBNSSwID -AQABo4ICEDCCAgwwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD -VR0OBBYEFE4L7xqkQFulF2mHMMo0aEPQQa7yMB8GA1UdIwQYMBaAFE4L7xqkQFul -F2mHMMo0aEPQQa7yMIIBWgYDVR0gBIIBUTCCAU0wggFJBgsrBgEEAYG1NwEBATCC -ATgwLgYIKwYBBQUHAgEWImh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL3BvbGljeS5w -ZGYwNAYIKwYBBQUHAgEWKGh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL2ludGVybWVk -aWF0ZS5wZGYwgc8GCCsGAQUFBwICMIHCMCcWIFN0YXJ0IENvbW1lcmNpYWwgKFN0 -YXJ0Q29tKSBMdGQuMAMCAQEagZZMaW1pdGVkIExpYWJpbGl0eSwgcmVhZCB0aGUg -c2VjdGlvbiAqTGVnYWwgTGltaXRhdGlvbnMqIG9mIHRoZSBTdGFydENvbSBDZXJ0 -aWZpY2F0aW9uIEF1dGhvcml0eSBQb2xpY3kgYXZhaWxhYmxlIGF0IGh0dHA6Ly93 -d3cuc3RhcnRzc2wuY29tL3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgG -CWCGSAGG+EIBDQQrFilTdGFydENvbSBGcmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1 -dGhvcml0eTANBgkqhkiG9w0BAQsFAAOCAgEAjo/n3JR5fPGFf59Jb2vKXfuM/gTF -wWLRfUKKvFO3lANmMD+x5wqnUCBVJX92ehQN6wQOQOY+2IirByeDqXWmN3PH/UvS -Ta0XQMhGvjt/UfzDtgUx3M2FIk5xt/JxXrAaxrqTi3iSSoX4eA+D/i+tLPfkpLst -0OcNOrg+zvZ49q5HJMqjNTbOx8aHmNrs++myziebiMMEofYLWWivydsQD032ZGNc -pRJvkrKTlMeIFw6Ttn5ii5B/q06f/ON1FE8qMt9bDeD1e5MNq6HPh+GlBEXoPBKl -CcWw0bdT82AUuoVpaiF8H3VhFyAXe2w7QSlc4axa0c2Mm+tgHRns9+Ww2vl5GKVF -P0lDV9LdJNUso/2RjSe15esUBppMeyG7Oq0wBhjA2MFrLH9ZXF2RsXAiV+uKa0hK -1Q8p7MZAwC+ITGgBF3f0JBlPvfrhsiAhS90a2Cl9qrjeVOwhVYBsHvUwyKMQ5bLm -KhQxw4UtjJixhlpPiVktucf3HMiKf8CdBUrmQk9io20ppB+Fq9vlgcitKj1MXVuE -JnHEhV5xJMqlG2zYYdMa4FTbzrqpMrUi9nNBCV24F10OD5mQ1kfabwo6YigUZ4LZ -8dCAWZvLMdibD4x3TrVoivJs9iQOLWxwxXPR3hTQcY+203sC9uO41Alua551hDnm -fyWl8kgAwKQB2j8= ------END CERTIFICATE----- - -# Issuer: CN=StartCom Certification Authority G2 O=StartCom Ltd. -# Subject: CN=StartCom Certification Authority G2 O=StartCom Ltd. -# Label: "StartCom Certification Authority G2" -# Serial: 59 -# MD5 Fingerprint: 78:4b:fb:9e:64:82:0a:d3:b8:4c:62:f3:64:f2:90:64 -# SHA1 Fingerprint: 31:f1:fd:68:22:63:20:ee:c6:3b:3f:9d:ea:4a:3e:53:7c:7c:39:17 -# SHA256 Fingerprint: c7:ba:65:67:de:93:a7:98:ae:1f:aa:79:1e:71:2d:37:8f:ae:1f:93:c4:39:7f:ea:44:1b:b7:cb:e6:fd:59:95 ------BEGIN CERTIFICATE----- -MIIFYzCCA0ugAwIBAgIBOzANBgkqhkiG9w0BAQsFADBTMQswCQYDVQQGEwJJTDEW -MBQGA1UEChMNU3RhcnRDb20gTHRkLjEsMCoGA1UEAxMjU3RhcnRDb20gQ2VydGlm -aWNhdGlvbiBBdXRob3JpdHkgRzIwHhcNMTAwMTAxMDEwMDAxWhcNMzkxMjMxMjM1 -OTAxWjBTMQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjEsMCoG -A1UEAxMjU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgRzIwggIiMA0G -CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2iTZbB7cgNr2Cu+EWIAOVeq8Oo1XJ -JZlKxdBWQYeQTSFgpBSHO839sj60ZwNq7eEPS8CRhXBF4EKe3ikj1AENoBB5uNsD -vfOpL9HG4A/LnooUCri99lZi8cVytjIl2bLzvWXFDSxu1ZJvGIsAQRSCb0AgJnoo -D/Uefyf3lLE3PbfHkffiAez9lInhzG7TNtYKGXmu1zSCZf98Qru23QumNK9LYP5/ -Q0kGi4xDuFby2X8hQxfqp0iVAXV16iulQ5XqFYSdCI0mblWbq9zSOdIxHWDirMxW -RST1HFSr7obdljKF+ExP6JV2tgXdNiNnvP8V4so75qbsO+wmETRIjfaAKxojAuuK -HDp2KntWFhxyKrOq42ClAJ8Em+JvHhRYW6Vsi1g8w7pOOlz34ZYrPu8HvKTlXcxN -nw3h3Kq74W4a7I/htkxNeXJdFzULHdfBR9qWJODQcqhaX2YtENwvKhOuJv4KHBnM -0D4LnMgJLvlblnpHnOl68wVQdJVznjAJ85eCXuaPOQgeWeU1FEIT/wCc976qUM/i -UUjXuG+v+E5+M5iSFGI6dWPPe/regjupuznixL0sAA7IF6wT700ljtizkC+p2il9 -Ha90OrInwMEePnWjFqmveiJdnxMaz6eg6+OGCtP95paV1yPIN93EfKo2rJgaErHg -TuixO/XWb/Ew1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE -AwIBBjAdBgNVHQ4EFgQUS8W0QGutHLOlHGVuRjaJhwUMDrYwDQYJKoZIhvcNAQEL -BQADggIBAHNXPyzVlTJ+N9uWkusZXn5T50HsEbZH77Xe7XRcxfGOSeD8bpkTzZ+K -2s06Ctg6Wgk/XzTQLwPSZh0avZyQN8gMjgdalEVGKua+etqhqaRpEpKwfTbURIfX -UfEpY9Z1zRbkJ4kd+MIySP3bmdCPX1R0zKxnNBFi2QwKN4fRoxdIjtIXHfbX/dtl -6/2o1PXWT6RbdejF0mCy2wl+JYt7ulKSnj7oxXehPOBKc2thz4bcQ///If4jXSRK -9dNtD2IEBVeC2m6kMyV5Sy5UGYvMLD0w6dEG/+gyRr61M3Z3qAFdlsHB1b6uJcDJ -HgoJIIihDsnzb02CVAAgp9KP5DlUFy6NHrgbuxu9mk47EDTcnIhT76IxW1hPkWLI -wpqazRVdOKnWvvgTtZ8SafJQYqz7Fzf07rh1Z2AQ+4NQ+US1dZxAF7L+/XldblhY -XzD8AK6vM8EOTmy6p6ahfzLbOOCxchcKK5HsamMm7YnUeMx0HgX4a/6ManY5Ka5l -IxKVCCIcl85bBu4M4ru8H0ST9tg4RQUh7eStqxK2A6RCLi3ECToDZ2mEmuFZkIoo -hdVddLHRDiBYmxOlsGOm7XtH/UVVMKTumtTm4ofvmMkyghEpIrwACjFeLQ/Ajulr -so8uBtjRkcfGEvRM/TAXw8HaOFvjqermobp573PYtlNXLfbQ4ddI ------END CERTIFICATE----- - -# Issuer: CN=DigiCert Global Root G2, OU=www.digicert.com, O=DigiCert Inc, C=US -# Subject: CN=DigiCert Global Root G2, OU=www.digicert.com, O=DigiCert Inc, C=US -# Serial: 33af1e6a711a9a0bb2864b11d09fae5 -# 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: /C=US/O=Internet Security Research Group/CN=ISRG Root X1 -# Subject: /C=US/O=Internet Security Research Group/CN=ISRG Root X1 -# Serial: 8210CFB0D240E3594463E0BB63828B00 -# 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: /C=US/O=Internet Security Research Group/CN=ISRG Root X2 -# Subject: /C=US/O=Internet Security Research Group/CN=ISRG Root X2 -# Serial: 41D29DD172EAEEA780C12C6CE92F8752 -# 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----- diff --git a/shotgun_api3/lib/httplib2/python2/certs.py b/shotgun_api3/lib/httplib2/python2/certs.py deleted file mode 100644 index 59d1ffc70..000000000 --- a/shotgun_api3/lib/httplib2/python2/certs.py +++ /dev/null @@ -1,42 +0,0 @@ -"""Utilities for certificate management.""" - -import os - -certifi_available = False -certifi_where = None -try: - from certifi import where as certifi_where - certifi_available = True -except ImportError: - pass - -custom_ca_locater_available = False -custom_ca_locater_where = None -try: - from ca_certs_locater import get as custom_ca_locater_where - custom_ca_locater_available = True -except ImportError: - pass - - -BUILTIN_CA_CERTS = os.path.join( - os.path.dirname(os.path.abspath(__file__)), "cacerts.txt" -) - - -def where(): - env = os.environ.get("HTTPLIB2_CA_CERTS") - if env is not None: - if os.path.isfile(env): - return env - else: - raise RuntimeError("Environment variable HTTPLIB2_CA_CERTS not a valid file") - if custom_ca_locater_available: - return custom_ca_locater_where() - if certifi_available: - return certifi_where() - return BUILTIN_CA_CERTS - - -if __name__ == "__main__": - print(where()) diff --git a/shotgun_api3/lib/httplib2/python2/error.py b/shotgun_api3/lib/httplib2/python2/error.py deleted file mode 100644 index 0e68c12a8..000000000 --- a/shotgun_api3/lib/httplib2/python2/error.py +++ /dev/null @@ -1,48 +0,0 @@ -# All exceptions raised here derive from HttpLib2Error -class HttpLib2Error(Exception): - pass - - -# Some exceptions can be caught and optionally -# be turned back into responses. -class HttpLib2ErrorWithResponse(HttpLib2Error): - def __init__(self, desc, response, content): - self.response = response - self.content = content - HttpLib2Error.__init__(self, desc) - - -class RedirectMissingLocation(HttpLib2ErrorWithResponse): - pass - - -class RedirectLimit(HttpLib2ErrorWithResponse): - pass - - -class FailedToDecompressContent(HttpLib2ErrorWithResponse): - pass - - -class UnimplementedDigestAuthOptionError(HttpLib2ErrorWithResponse): - pass - - -class UnimplementedHmacDigestAuthOptionError(HttpLib2ErrorWithResponse): - pass - - -class MalformedHeader(HttpLib2Error): - pass - - -class RelativeURIError(HttpLib2Error): - pass - - -class ServerNotFoundError(HttpLib2Error): - pass - - -class ProxiesUnavailableError(HttpLib2Error): - pass diff --git a/shotgun_api3/lib/httplib2/python2/iri2uri.py b/shotgun_api3/lib/httplib2/python2/iri2uri.py deleted file mode 100644 index 0a978a784..000000000 --- a/shotgun_api3/lib/httplib2/python2/iri2uri.py +++ /dev/null @@ -1,123 +0,0 @@ -"""Converts an IRI to a URI.""" - -__author__ = "Joe Gregorio (joe@bitworking.org)" -__copyright__ = "Copyright 2006, Joe Gregorio" -__contributors__ = [] -__version__ = "1.0.0" -__license__ = "MIT" - -import urlparse - -# Convert an IRI to a URI following the rules in RFC 3987 -# -# The characters we need to enocde and escape are defined in the spec: -# -# iprivate = %xE000-F8FF / %xF0000-FFFFD / %x100000-10FFFD -# ucschar = %xA0-D7FF / %xF900-FDCF / %xFDF0-FFEF -# / %x10000-1FFFD / %x20000-2FFFD / %x30000-3FFFD -# / %x40000-4FFFD / %x50000-5FFFD / %x60000-6FFFD -# / %x70000-7FFFD / %x80000-8FFFD / %x90000-9FFFD -# / %xA0000-AFFFD / %xB0000-BFFFD / %xC0000-CFFFD -# / %xD0000-DFFFD / %xE1000-EFFFD - -escape_range = [ - (0xA0, 0xD7FF), - (0xE000, 0xF8FF), - (0xF900, 0xFDCF), - (0xFDF0, 0xFFEF), - (0x10000, 0x1FFFD), - (0x20000, 0x2FFFD), - (0x30000, 0x3FFFD), - (0x40000, 0x4FFFD), - (0x50000, 0x5FFFD), - (0x60000, 0x6FFFD), - (0x70000, 0x7FFFD), - (0x80000, 0x8FFFD), - (0x90000, 0x9FFFD), - (0xA0000, 0xAFFFD), - (0xB0000, 0xBFFFD), - (0xC0000, 0xCFFFD), - (0xD0000, 0xDFFFD), - (0xE1000, 0xEFFFD), - (0xF0000, 0xFFFFD), - (0x100000, 0x10FFFD), -] - - -def encode(c): - retval = c - i = ord(c) - for low, high in escape_range: - if i < low: - break - if i >= low and i <= high: - retval = "".join(["%%%2X" % ord(o) for o in c.encode("utf-8")]) - break - return retval - - -def iri2uri(uri): - """Convert an IRI to a URI. Note that IRIs must be - passed in a unicode strings. That is, do not utf-8 encode - the IRI before passing it into the function.""" - if isinstance(uri, unicode): - (scheme, authority, path, query, fragment) = urlparse.urlsplit(uri) - authority = authority.encode("idna") - # For each character in 'ucschar' or 'iprivate' - # 1. encode as utf-8 - # 2. then %-encode each octet of that utf-8 - uri = urlparse.urlunsplit((scheme, authority, path, query, fragment)) - uri = "".join([encode(c) for c in uri]) - return uri - - -if __name__ == "__main__": - import unittest - - class Test(unittest.TestCase): - def test_uris(self): - """Test that URIs are invariant under the transformation.""" - invariant = [ - u"ftp://ftp.is.co.za/rfc/rfc1808.txt", - u"http://www.ietf.org/rfc/rfc2396.txt", - u"ldap://[2001:db8::7]/c=GB?objectClass?one", - u"mailto:John.Doe@example.com", - u"news:comp.infosystems.www.servers.unix", - u"tel:+1-816-555-1212", - u"telnet://192.0.2.16:80/", - u"urn:oasis:names:specification:docbook:dtd:xml:4.1.2", - ] - for uri in invariant: - self.assertEqual(uri, iri2uri(uri)) - - def test_iri(self): - """Test that the right type of escaping is done for each part of the URI.""" - self.assertEqual( - "http://xn--o3h.com/%E2%98%84", - iri2uri(u"http://\N{COMET}.com/\N{COMET}"), - ) - self.assertEqual( - "http://bitworking.org/?fred=%E2%98%84", - iri2uri(u"http://bitworking.org/?fred=\N{COMET}"), - ) - self.assertEqual( - "http://bitworking.org/#%E2%98%84", - iri2uri(u"http://bitworking.org/#\N{COMET}"), - ) - self.assertEqual("#%E2%98%84", iri2uri(u"#\N{COMET}")) - self.assertEqual( - "/fred?bar=%E2%98%9A#%E2%98%84", - iri2uri(u"/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}"), - ) - self.assertEqual( - "/fred?bar=%E2%98%9A#%E2%98%84", - iri2uri(iri2uri(u"/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}")), - ) - self.assertNotEqual( - "/fred?bar=%E2%98%9A#%E2%98%84", - iri2uri( - u"/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}".encode("utf-8") - ), - ) - - unittest.main() diff --git a/shotgun_api3/lib/httplib2/python2/socks.py b/shotgun_api3/lib/httplib2/python2/socks.py deleted file mode 100644 index 71eb4ebf9..000000000 --- a/shotgun_api3/lib/httplib2/python2/socks.py +++ /dev/null @@ -1,518 +0,0 @@ -"""SocksiPy - Python SOCKS module. - -Version 1.00 - -Copyright 2006 Dan-Haim. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. -3. Neither the name of Dan Haim nor the names of his contributors may be used - to endorse or promote products derived from this software without specific - prior written permission. - -THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED -WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO -EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT -OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE. - -This module provides a standard socket-like interface for Python -for tunneling connections through SOCKS proxies. - -Minor modifications made by Christopher Gilbert (http://motomastyle.com/) for -use in PyLoris (http://pyloris.sourceforge.net/). - -Minor modifications made by Mario Vilas (http://breakingcode.wordpress.com/) -mainly to merge bug fixes found in Sourceforge. -""" - -import base64 -import socket -import struct -import sys - -if getattr(socket, "socket", None) is None: - raise ImportError("socket.socket missing, proxy support unusable") - -PROXY_TYPE_SOCKS4 = 1 -PROXY_TYPE_SOCKS5 = 2 -PROXY_TYPE_HTTP = 3 -PROXY_TYPE_HTTP_NO_TUNNEL = 4 - -_defaultproxy = None -_orgsocket = socket.socket - - -class ProxyError(Exception): - pass - - -class GeneralProxyError(ProxyError): - pass - - -class Socks5AuthError(ProxyError): - pass - - -class Socks5Error(ProxyError): - pass - - -class Socks4Error(ProxyError): - pass - - -class HTTPError(ProxyError): - pass - - -_generalerrors = ( - "success", - "invalid data", - "not connected", - "not available", - "bad proxy type", - "bad input", -) - -_socks5errors = ( - "succeeded", - "general SOCKS server failure", - "connection not allowed by ruleset", - "Network unreachable", - "Host unreachable", - "Connection refused", - "TTL expired", - "Command not supported", - "Address type not supported", - "Unknown error", -) - -_socks5autherrors = ( - "succeeded", - "authentication is required", - "all offered authentication methods were rejected", - "unknown username or invalid password", - "unknown error", -) - -_socks4errors = ( - "request granted", - "request rejected or failed", - "request rejected because SOCKS server cannot connect to identd on the client", - "request rejected because the client program and identd report different " - "user-ids", - "unknown error", -) - - -def setdefaultproxy( - proxytype=None, addr=None, port=None, rdns=True, username=None, password=None -): - """setdefaultproxy(proxytype, addr[, port[, rdns[, username[, password]]]]) - Sets a default proxy which all further socksocket objects will use, - unless explicitly changed. - """ - global _defaultproxy - _defaultproxy = (proxytype, addr, port, rdns, username, password) - - -def wrapmodule(module): - """wrapmodule(module) - - Attempts to replace a module's socket library with a SOCKS socket. Must set - a default proxy using setdefaultproxy(...) first. - This will only work on modules that import socket directly into the - namespace; - most of the Python Standard Library falls into this category. - """ - if _defaultproxy != None: - module.socket.socket = socksocket - else: - raise GeneralProxyError((4, "no proxy specified")) - - -class socksocket(socket.socket): - """socksocket([family[, type[, proto]]]) -> socket object - Open a SOCKS enabled socket. The parameters are the same as - those of the standard socket init. In order for SOCKS to work, - you must specify family=AF_INET, type=SOCK_STREAM and proto=0. - """ - - def __init__( - self, family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0, _sock=None - ): - _orgsocket.__init__(self, family, type, proto, _sock) - if _defaultproxy != None: - self.__proxy = _defaultproxy - else: - self.__proxy = (None, None, None, None, None, None) - self.__proxysockname = None - self.__proxypeername = None - self.__httptunnel = True - - def __recvall(self, count): - """__recvall(count) -> data - Receive EXACTLY the number of bytes requested from the socket. - Blocks until the required number of bytes have been received. - """ - data = self.recv(count) - while len(data) < count: - d = self.recv(count - len(data)) - if not d: - raise GeneralProxyError((0, "connection closed unexpectedly")) - data = data + d - return data - - def sendall(self, content, *args): - """ override socket.socket.sendall method to rewrite the header - for non-tunneling proxies if needed - """ - if not self.__httptunnel: - content = self.__rewriteproxy(content) - return super(socksocket, self).sendall(content, *args) - - def __rewriteproxy(self, header): - """ rewrite HTTP request headers to support non-tunneling proxies - (i.e. those which do not support the CONNECT method). - This only works for HTTP (not HTTPS) since HTTPS requires tunneling. - """ - host, endpt = None, None - hdrs = header.split("\r\n") - for hdr in hdrs: - if hdr.lower().startswith("host:"): - host = hdr - elif hdr.lower().startswith("get") or hdr.lower().startswith("post"): - endpt = hdr - if host and endpt: - hdrs.remove(host) - hdrs.remove(endpt) - host = host.split(" ")[1] - endpt = endpt.split(" ") - if self.__proxy[4] != None and self.__proxy[5] != None: - hdrs.insert(0, self.__getauthheader()) - hdrs.insert(0, "Host: %s" % host) - hdrs.insert(0, "%s http://%s%s %s" % (endpt[0], host, endpt[1], endpt[2])) - return "\r\n".join(hdrs) - - def __getauthheader(self): - auth = self.__proxy[4] + ":" + self.__proxy[5] - return "Proxy-Authorization: Basic " + base64.b64encode(auth) - - def setproxy( - self, - proxytype=None, - addr=None, - port=None, - rdns=True, - username=None, - password=None, - headers=None, - ): - """setproxy(proxytype, addr[, port[, rdns[, username[, password]]]]) - - Sets the proxy to be used. - proxytype - The type of the proxy to be used. Three types - are supported: PROXY_TYPE_SOCKS4 (including socks4a), - PROXY_TYPE_SOCKS5 and PROXY_TYPE_HTTP - addr - The address of the server (IP or DNS). - port - The port of the server. Defaults to 1080 for SOCKS - servers and 8080 for HTTP proxy servers. - rdns - Should DNS queries be preformed on the remote side - (rather than the local side). The default is True. - Note: This has no effect with SOCKS4 servers. - username - Username to authenticate with to the server. - The default is no authentication. - password - Password to authenticate with to the server. - Only relevant when username is also provided. - headers - Additional or modified headers for the proxy connect - request. - """ - self.__proxy = ( - proxytype, - addr, - port, - rdns, - username.encode() if username else None, - password.encode() if password else None, - headers, - ) - - def __negotiatesocks5(self, destaddr, destport): - """__negotiatesocks5(self,destaddr,destport) - Negotiates a connection through a SOCKS5 server. - """ - # First we'll send the authentication packages we support. - if (self.__proxy[4] != None) and (self.__proxy[5] != None): - # The username/password details were supplied to the - # setproxy method so we support the USERNAME/PASSWORD - # authentication (in addition to the standard none). - self.sendall(struct.pack("BBBB", 0x05, 0x02, 0x00, 0x02)) - else: - # No username/password were entered, therefore we - # only support connections with no authentication. - self.sendall(struct.pack("BBB", 0x05, 0x01, 0x00)) - # We'll receive the server's response to determine which - # method was selected - chosenauth = self.__recvall(2) - if chosenauth[0:1] != chr(0x05).encode(): - self.close() - raise GeneralProxyError((1, _generalerrors[1])) - # Check the chosen authentication method - if chosenauth[1:2] == chr(0x00).encode(): - # No authentication is required - pass - elif chosenauth[1:2] == chr(0x02).encode(): - # Okay, we need to perform a basic username/password - # authentication. - self.sendall( - chr(0x01).encode() - + chr(len(self.__proxy[4])) - + self.__proxy[4] - + chr(len(self.__proxy[5])) - + self.__proxy[5] - ) - authstat = self.__recvall(2) - if authstat[0:1] != chr(0x01).encode(): - # Bad response - self.close() - raise GeneralProxyError((1, _generalerrors[1])) - if authstat[1:2] != chr(0x00).encode(): - # Authentication failed - self.close() - raise Socks5AuthError((3, _socks5autherrors[3])) - # Authentication succeeded - else: - # Reaching here is always bad - self.close() - if chosenauth[1] == chr(0xFF).encode(): - raise Socks5AuthError((2, _socks5autherrors[2])) - else: - raise GeneralProxyError((1, _generalerrors[1])) - # Now we can request the actual connection - req = struct.pack("BBB", 0x05, 0x01, 0x00) - # If the given destination address is an IP address, we'll - # use the IPv4 address request even if remote resolving was specified. - try: - ipaddr = socket.inet_aton(destaddr) - req = req + chr(0x01).encode() + ipaddr - except socket.error: - # Well it's not an IP number, so it's probably a DNS name. - if self.__proxy[3]: - # Resolve remotely - ipaddr = None - req = ( - req - + chr(0x03).encode() - + chr(len(destaddr)).encode() - + destaddr.encode() - ) - else: - # Resolve locally - ipaddr = socket.inet_aton(socket.gethostbyname(destaddr)) - req = req + chr(0x01).encode() + ipaddr - req = req + struct.pack(">H", destport) - self.sendall(req) - # Get the response - resp = self.__recvall(4) - if resp[0:1] != chr(0x05).encode(): - self.close() - raise GeneralProxyError((1, _generalerrors[1])) - elif resp[1:2] != chr(0x00).encode(): - # Connection failed - self.close() - if ord(resp[1:2]) <= 8: - raise Socks5Error((ord(resp[1:2]), _socks5errors[ord(resp[1:2])])) - else: - raise Socks5Error((9, _socks5errors[9])) - # Get the bound address/port - elif resp[3:4] == chr(0x01).encode(): - boundaddr = self.__recvall(4) - elif resp[3:4] == chr(0x03).encode(): - resp = resp + self.recv(1) - boundaddr = self.__recvall(ord(resp[4:5])) - else: - self.close() - raise GeneralProxyError((1, _generalerrors[1])) - boundport = struct.unpack(">H", self.__recvall(2))[0] - self.__proxysockname = (boundaddr, boundport) - if ipaddr != None: - self.__proxypeername = (socket.inet_ntoa(ipaddr), destport) - else: - self.__proxypeername = (destaddr, destport) - - def getproxysockname(self): - """getsockname() -> address info - Returns the bound IP address and port number at the proxy. - """ - return self.__proxysockname - - def getproxypeername(self): - """getproxypeername() -> address info - Returns the IP and port number of the proxy. - """ - return _orgsocket.getpeername(self) - - def getpeername(self): - """getpeername() -> address info - Returns the IP address and port number of the destination - machine (note: getproxypeername returns the proxy) - """ - return self.__proxypeername - - def __negotiatesocks4(self, destaddr, destport): - """__negotiatesocks4(self,destaddr,destport) - Negotiates a connection through a SOCKS4 server. - """ - # Check if the destination address provided is an IP address - rmtrslv = False - try: - ipaddr = socket.inet_aton(destaddr) - except socket.error: - # It's a DNS name. Check where it should be resolved. - if self.__proxy[3]: - ipaddr = struct.pack("BBBB", 0x00, 0x00, 0x00, 0x01) - rmtrslv = True - else: - ipaddr = socket.inet_aton(socket.gethostbyname(destaddr)) - # Construct the request packet - req = struct.pack(">BBH", 0x04, 0x01, destport) + ipaddr - # The username parameter is considered userid for SOCKS4 - if self.__proxy[4] != None: - req = req + self.__proxy[4] - req = req + chr(0x00).encode() - # DNS name if remote resolving is required - # NOTE: This is actually an extension to the SOCKS4 protocol - # called SOCKS4A and may not be supported in all cases. - if rmtrslv: - req = req + destaddr + chr(0x00).encode() - self.sendall(req) - # Get the response from the server - resp = self.__recvall(8) - if resp[0:1] != chr(0x00).encode(): - # Bad data - self.close() - raise GeneralProxyError((1, _generalerrors[1])) - if resp[1:2] != chr(0x5A).encode(): - # Server returned an error - self.close() - if ord(resp[1:2]) in (91, 92, 93): - self.close() - raise Socks4Error((ord(resp[1:2]), _socks4errors[ord(resp[1:2]) - 90])) - else: - raise Socks4Error((94, _socks4errors[4])) - # Get the bound address/port - self.__proxysockname = ( - socket.inet_ntoa(resp[4:]), - struct.unpack(">H", resp[2:4])[0], - ) - if rmtrslv != None: - self.__proxypeername = (socket.inet_ntoa(ipaddr), destport) - else: - self.__proxypeername = (destaddr, destport) - - def __negotiatehttp(self, destaddr, destport): - """__negotiatehttp(self,destaddr,destport) - Negotiates a connection through an HTTP server. - """ - # If we need to resolve locally, we do this now - if not self.__proxy[3]: - addr = socket.gethostbyname(destaddr) - else: - addr = destaddr - headers = ["CONNECT ", addr, ":", str(destport), " HTTP/1.1\r\n"] - wrote_host_header = False - wrote_auth_header = False - if self.__proxy[6] != None: - for key, val in self.__proxy[6].iteritems(): - headers += [key, ": ", val, "\r\n"] - wrote_host_header = key.lower() == "host" - wrote_auth_header = key.lower() == "proxy-authorization" - if not wrote_host_header: - headers += ["Host: ", destaddr, "\r\n"] - if not wrote_auth_header: - if self.__proxy[4] != None and self.__proxy[5] != None: - headers += [self.__getauthheader(), "\r\n"] - headers.append("\r\n") - self.sendall("".join(headers).encode()) - # We read the response until we get the string "\r\n\r\n" - resp = self.recv(1) - while resp.find("\r\n\r\n".encode()) == -1: - resp = resp + self.recv(1) - # We just need the first line to check if the connection - # was successful - statusline = resp.splitlines()[0].split(" ".encode(), 2) - if statusline[0] not in ("HTTP/1.0".encode(), "HTTP/1.1".encode()): - self.close() - raise GeneralProxyError((1, _generalerrors[1])) - try: - statuscode = int(statusline[1]) - except ValueError: - self.close() - raise GeneralProxyError((1, _generalerrors[1])) - if statuscode != 200: - self.close() - raise HTTPError((statuscode, statusline[2])) - self.__proxysockname = ("0.0.0.0", 0) - self.__proxypeername = (addr, destport) - - def connect(self, destpair): - """connect(self, despair) - Connects to the specified destination through a proxy. - destpar - A tuple of the IP/DNS address and the port number. - (identical to socket's connect). - To select the proxy server use setproxy(). - """ - # Do a minimal input check first - if ( - (not type(destpair) in (list, tuple)) - or (len(destpair) < 2) - or (not isinstance(destpair[0], basestring)) - or (type(destpair[1]) != int) - ): - raise GeneralProxyError((5, _generalerrors[5])) - if self.__proxy[0] == PROXY_TYPE_SOCKS5: - if self.__proxy[2] != None: - portnum = self.__proxy[2] - else: - portnum = 1080 - _orgsocket.connect(self, (self.__proxy[1], portnum)) - self.__negotiatesocks5(destpair[0], destpair[1]) - elif self.__proxy[0] == PROXY_TYPE_SOCKS4: - if self.__proxy[2] != None: - portnum = self.__proxy[2] - else: - portnum = 1080 - _orgsocket.connect(self, (self.__proxy[1], portnum)) - self.__negotiatesocks4(destpair[0], destpair[1]) - elif self.__proxy[0] == PROXY_TYPE_HTTP: - if self.__proxy[2] != None: - portnum = self.__proxy[2] - else: - portnum = 8080 - _orgsocket.connect(self, (self.__proxy[1], portnum)) - self.__negotiatehttp(destpair[0], destpair[1]) - elif self.__proxy[0] == PROXY_TYPE_HTTP_NO_TUNNEL: - if self.__proxy[2] != None: - portnum = self.__proxy[2] - else: - portnum = 8080 - _orgsocket.connect(self, (self.__proxy[1], portnum)) - if destpair[1] == 443: - self.__negotiatehttp(destpair[0], destpair[1]) - else: - self.__httptunnel = False - elif self.__proxy[0] == None: - _orgsocket.connect(self, (destpair[0], destpair[1])) - else: - raise GeneralProxyError((4, _generalerrors[4])) diff --git a/shotgun_api3/lib/httplib2/python3/__init__.py b/shotgun_api3/lib/httplib2/python3/__init__.py deleted file mode 100644 index ba5fa2f23..000000000 --- a/shotgun_api3/lib/httplib2/python3/__init__.py +++ /dev/null @@ -1,1799 +0,0 @@ -# -*- coding: utf-8 -*- -"""Small, fast HTTP client library for Python.""" - -__author__ = "Joe Gregorio (joe@bitworking.org)" -__copyright__ = "Copyright 2006, Joe Gregorio" -__contributors__ = [ - "Thomas Broyer (t.broyer@ltgt.net)", - "James Antill", - "Xavier Verges Farrero", - "Jonathan Feinberg", - "Blair Zajac", - "Sam Ruby", - "Louis Nyffenegger", - "Mark Pilgrim", - "Alex Yu", - "Lai Han", -] -__license__ = "MIT" -__version__ = "0.22.0" - -import base64 -import calendar -import copy -import email -import email.feedparser -from email import header -import email.message -import email.utils -import errno -from gettext import gettext as _ -import gzip -from hashlib import md5 as _md5 -from hashlib import sha1 as _sha -import hmac -import http.client -import io -import os -import random -import re -import socket -import ssl -import sys -import time -import urllib.parse -import zlib - -try: - import socks -except ImportError: - # TODO: remove this fallback and copypasted socksipy module upon py2/3 merge, - # idea is to have soft-dependency on any compatible module called socks - from . import socks -from . import auth -from .error import * -from .iri2uri import iri2uri - - -def has_timeout(timeout): - if hasattr(socket, "_GLOBAL_DEFAULT_TIMEOUT"): - return timeout is not None and timeout is not socket._GLOBAL_DEFAULT_TIMEOUT - return timeout is not None - - -__all__ = [ - "debuglevel", - "FailedToDecompressContent", - "Http", - "HttpLib2Error", - "ProxyInfo", - "RedirectLimit", - "RedirectMissingLocation", - "Response", - "RETRIES", - "UnimplementedDigestAuthOptionError", - "UnimplementedHmacDigestAuthOptionError", -] - -# The httplib debug level, set to a non-zero value to get debug output -debuglevel = 0 - -# A request will be tried 'RETRIES' times if it fails at the socket/connection level. -RETRIES = 2 - - -# Open Items: -# ----------- - -# Are we removing the cached content too soon on PUT (only delete on 200 Maybe?) - -# Pluggable cache storage (supports storing the cache in -# flat files by default. We need a plug-in architecture -# that can support Berkeley DB and Squid) - -# == Known Issues == -# Does not handle a resource that uses conneg and Last-Modified but no ETag as a cache validator. -# Does not handle Cache-Control: max-stale -# Does not use Age: headers when calculating cache freshness. - -# The number of redirections to follow before giving up. -# Note that only GET redirects are automatically followed. -# Will also honor 301 requests by saving that info and never -# requesting that URI again. -DEFAULT_MAX_REDIRECTS = 5 - -# Which headers are hop-by-hop headers by default -HOP_BY_HOP = [ - "connection", - "keep-alive", - "proxy-authenticate", - "proxy-authorization", - "te", - "trailers", - "transfer-encoding", - "upgrade", -] - -# https://tools.ietf.org/html/rfc7231#section-8.1.3 -SAFE_METHODS = ("GET", "HEAD", "OPTIONS", "TRACE") - -# To change, assign to `Http().redirect_codes` -REDIRECT_CODES = frozenset((300, 301, 302, 303, 307, 308)) - - -from . import certs - -CA_CERTS = certs.where() - -# PROTOCOL_TLS is python 3.5.3+. PROTOCOL_SSLv23 is deprecated. -# Both PROTOCOL_TLS and PROTOCOL_SSLv23 are equivalent and means: -# > Selects the highest protocol version that both the client and server support. -# > Despite the name, this option can select “TLS” protocols as well as “SSL”. -# source: https://docs.python.org/3.5/library/ssl.html#ssl.PROTOCOL_SSLv23 - -# PROTOCOL_TLS_CLIENT is python 3.10.0+. PROTOCOL_TLS is deprecated. -# > Auto-negotiate the highest protocol version that both the client and server support, and configure the context client-side connections. -# > The protocol enables CERT_REQUIRED and check_hostname by default. -# source: https://docs.python.org/3.10/library/ssl.html#ssl.PROTOCOL_TLS - -DEFAULT_TLS_VERSION = getattr(ssl, "PROTOCOL_TLS_CLIENT", None) or getattr(ssl, "PROTOCOL_TLS", None) or getattr(ssl, "PROTOCOL_SSLv23") - - -def _build_ssl_context( - disable_ssl_certificate_validation, - ca_certs, - cert_file=None, - key_file=None, - maximum_version=None, - minimum_version=None, - key_password=None, -): - if not hasattr(ssl, "SSLContext"): - raise RuntimeError("httplib2 requires Python 3.2+ for ssl.SSLContext") - - context = ssl.SSLContext(DEFAULT_TLS_VERSION) - # check_hostname and verify_mode should be set in opposite order during disable - # https://bugs.python.org/issue31431 - if disable_ssl_certificate_validation and hasattr(context, "check_hostname"): - context.check_hostname = not disable_ssl_certificate_validation - context.verify_mode = ssl.CERT_NONE if disable_ssl_certificate_validation else ssl.CERT_REQUIRED - - # SSLContext.maximum_version and SSLContext.minimum_version are python 3.7+. - # source: https://docs.python.org/3/library/ssl.html#ssl.SSLContext.maximum_version - if maximum_version is not None: - if hasattr(context, "maximum_version"): - if isinstance(maximum_version, str): - maximum_version = getattr(ssl.TLSVersion, maximum_version) - context.maximum_version = maximum_version - else: - raise RuntimeError("setting tls_maximum_version requires Python 3.7 and OpenSSL 1.1 or newer") - if minimum_version is not None: - if hasattr(context, "minimum_version"): - if isinstance(minimum_version, str): - minimum_version = getattr(ssl.TLSVersion, minimum_version) - context.minimum_version = minimum_version - else: - raise RuntimeError("setting tls_minimum_version requires Python 3.7 and OpenSSL 1.1 or newer") - # check_hostname requires python 3.4+ - # we will perform the equivalent in HTTPSConnectionWithTimeout.connect() by calling ssl.match_hostname - # if check_hostname is not supported. - if hasattr(context, "check_hostname"): - context.check_hostname = not disable_ssl_certificate_validation - - context.load_verify_locations(ca_certs) - - if cert_file: - context.load_cert_chain(cert_file, key_file, key_password) - - return context - - -def _get_end2end_headers(response): - hopbyhop = list(HOP_BY_HOP) - hopbyhop.extend([x.strip() for x in response.get("connection", "").split(",")]) - return [header for header in list(response.keys()) if header not in hopbyhop] - - -_missing = object() - - -def _errno_from_exception(e): - # TODO python 3.11+ cheap try: return e.errno except AttributeError: pass - errno = getattr(e, "errno", _missing) - if errno is not _missing: - return errno - - # socket.error and common wrap in .args - args = getattr(e, "args", None) - if args: - return _errno_from_exception(args[0]) - - # pysocks.ProxyError wraps in .socket_err - # https://github.com/httplib2/httplib2/pull/202 - socket_err = getattr(e, "socket_err", None) - if socket_err: - return _errno_from_exception(socket_err) - - return None - - -URI = re.compile(r"^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?") - - -def parse_uri(uri): - """Parses a URI using the regex given in Appendix B of RFC 3986. - - (scheme, authority, path, query, fragment) = parse_uri(uri) - """ - groups = URI.match(uri).groups() - return (groups[1], groups[3], groups[4], groups[6], groups[8]) - - -def urlnorm(uri): - (scheme, authority, path, query, fragment) = parse_uri(uri) - if not scheme or not authority: - raise RelativeURIError("Only absolute URIs are allowed. uri = %s" % uri) - authority = authority.lower() - scheme = scheme.lower() - if not path: - path = "/" - # Could do syntax based normalization of the URI before - # computing the digest. See Section 6.2.2 of Std 66. - request_uri = query and "?".join([path, query]) or path - scheme = scheme.lower() - defrag_uri = scheme + "://" + authority + request_uri - return scheme, authority, request_uri, defrag_uri - - -# Cache filename construction (original borrowed from Venus http://intertwingly.net/code/venus/) -re_url_scheme = re.compile(r"^\w+://") -re_unsafe = re.compile(r"[^\w\-_.()=!]+", re.ASCII) - - -def safename(filename): - """Return a filename suitable for the cache. - Strips dangerous and common characters to create a filename we - can use to store the cache in. - """ - if isinstance(filename, bytes): - filename_bytes = filename - filename = filename.decode("utf-8") - else: - filename_bytes = filename.encode("utf-8") - filemd5 = _md5(filename_bytes).hexdigest() - filename = re_url_scheme.sub("", filename) - filename = re_unsafe.sub("", filename) - - # limit length of filename (vital for Windows) - # https://github.com/httplib2/httplib2/pull/74 - # C:\Users\ \AppData\Local\Temp\ , - # 9 chars + max 104 chars + 20 chars + x + 1 + 32 = max 259 chars - # Thus max safe filename x = 93 chars. Let it be 90 to make a round sum: - filename = filename[:90] - - return ",".join((filename, filemd5)) - - -NORMALIZE_SPACE = re.compile(r"(?:\r\n)?[ \t]+") - - -def _normalize_headers(headers): - return dict( - [ - (_convert_byte_str(key).lower(), NORMALIZE_SPACE.sub(_convert_byte_str(value), " ").strip(),) - for (key, value) in headers.items() - ] - ) - - -def _convert_byte_str(s): - if not isinstance(s, str): - return str(s, "utf-8") - return s - - -def _parse_cache_control(headers): - retval = {} - if "cache-control" in headers: - parts = headers["cache-control"].split(",") - parts_with_args = [ - tuple([x.strip().lower() for x in part.split("=", 1)]) for part in parts if -1 != part.find("=") - ] - parts_wo_args = [(name.strip().lower(), 1) for name in parts if -1 == name.find("=")] - retval = dict(parts_with_args + parts_wo_args) - return retval - - -# Whether to use a strict mode to parse WWW-Authenticate headers -# Might lead to bad results in case of ill-formed header value, -# so disabled by default, falling back to relaxed parsing. -# Set to true to turn on, useful for testing servers. -USE_WWW_AUTH_STRICT_PARSING = 0 - - -def _entry_disposition(response_headers, request_headers): - """Determine freshness from the Date, Expires and Cache-Control headers. - - We don't handle the following: - - 1. Cache-Control: max-stale - 2. Age: headers are not used in the calculations. - - Not that this algorithm is simpler than you might think - because we are operating as a private (non-shared) cache. - This lets us ignore 's-maxage'. We can also ignore - 'proxy-invalidate' since we aren't a proxy. - We will never return a stale document as - fresh as a design decision, and thus the non-implementation - of 'max-stale'. This also lets us safely ignore 'must-revalidate' - since we operate as if every server has sent 'must-revalidate'. - Since we are private we get to ignore both 'public' and - 'private' parameters. We also ignore 'no-transform' since - we don't do any transformations. - The 'no-store' parameter is handled at a higher level. - So the only Cache-Control parameters we look at are: - - no-cache - only-if-cached - max-age - min-fresh - """ - - retval = "STALE" - cc = _parse_cache_control(request_headers) - cc_response = _parse_cache_control(response_headers) - - if "pragma" in request_headers and request_headers["pragma"].lower().find("no-cache") != -1: - retval = "TRANSPARENT" - if "cache-control" not in request_headers: - request_headers["cache-control"] = "no-cache" - elif "no-cache" in cc: - retval = "TRANSPARENT" - elif "no-cache" in cc_response: - retval = "STALE" - elif "only-if-cached" in cc: - retval = "FRESH" - elif "date" in response_headers: - date = calendar.timegm(email.utils.parsedate_tz(response_headers["date"])) - now = time.time() - current_age = max(0, now - date) - if "max-age" in cc_response: - try: - freshness_lifetime = int(cc_response["max-age"]) - except ValueError: - freshness_lifetime = 0 - elif "expires" in response_headers: - expires = email.utils.parsedate_tz(response_headers["expires"]) - if None == expires: - freshness_lifetime = 0 - else: - freshness_lifetime = max(0, calendar.timegm(expires) - date) - else: - freshness_lifetime = 0 - if "max-age" in cc: - try: - freshness_lifetime = int(cc["max-age"]) - except ValueError: - freshness_lifetime = 0 - if "min-fresh" in cc: - try: - min_fresh = int(cc["min-fresh"]) - except ValueError: - min_fresh = 0 - current_age += min_fresh - if freshness_lifetime > current_age: - retval = "FRESH" - return retval - - -def _decompressContent(response, new_content): - content = new_content - try: - encoding = response.get("content-encoding", None) - if encoding in ["gzip", "deflate"]: - if encoding == "gzip": - content = gzip.GzipFile(fileobj=io.BytesIO(new_content)).read() - if encoding == "deflate": - try: - content = zlib.decompress(content, zlib.MAX_WBITS) - except (IOError, zlib.error): - content = zlib.decompress(content, -zlib.MAX_WBITS) - response["content-length"] = str(len(content)) - # Record the historical presence of the encoding in a way the won't interfere. - response["-content-encoding"] = response["content-encoding"] - del response["content-encoding"] - except (IOError, zlib.error): - content = "" - raise FailedToDecompressContent( - _("Content purported to be compressed with %s but failed to decompress.") % response.get("content-encoding"), - response, - content, - ) - return content - - -def _bind_write_headers(msg): - def _write_headers(self): - # Self refers to the Generator object. - for h, v in msg.items(): - print("%s:" % h, end=" ", file=self._fp) - if isinstance(v, header.Header): - print(v.encode(maxlinelen=self._maxheaderlen), file=self._fp) - else: - # email.Header got lots of smarts, so use it. - headers = header.Header(v, maxlinelen=self._maxheaderlen, charset="utf-8", header_name=h) - print(headers.encode(), file=self._fp) - # A blank line always separates headers from body. - print(file=self._fp) - - return _write_headers - - -def _updateCache(request_headers, response_headers, content, cache, cachekey): - if cachekey: - cc = _parse_cache_control(request_headers) - cc_response = _parse_cache_control(response_headers) - if "no-store" in cc or "no-store" in cc_response: - cache.delete(cachekey) - else: - info = email.message.Message() - for key, value in response_headers.items(): - if key not in ["status", "content-encoding", "transfer-encoding"]: - info[key] = value - - # Add annotations to the cache to indicate what headers - # are variant for this request. - vary = response_headers.get("vary", None) - if vary: - vary_headers = vary.lower().replace(" ", "").split(",") - for header in vary_headers: - key = "-varied-%s" % header - try: - info[key] = request_headers[header] - except KeyError: - pass - - status = response_headers.status - if status == 304: - status = 200 - - status_header = "status: %d\r\n" % status - - try: - header_str = info.as_string() - except UnicodeEncodeError: - setattr(info, "_write_headers", _bind_write_headers(info)) - header_str = info.as_string() - - header_str = re.sub("\r(?!\n)|(? 0: - service = "cl" - # No point in guessing Base or Spreadsheet - # elif request_uri.find("spreadsheets") > 0: - # service = "wise" - - auth = dict(Email=credentials[0], Passwd=credentials[1], service=service, source=headers["user-agent"],) - resp, content = self.http.request( - "https://www.google.com/accounts/ClientLogin", - method="POST", - body=urlencode(auth), - headers={"Content-Type": "application/x-www-form-urlencoded"}, - ) - lines = content.split("\n") - d = dict([tuple(line.split("=", 1)) for line in lines if line]) - if resp.status == 403: - self.Auth = "" - else: - self.Auth = d["Auth"] - - def request(self, method, request_uri, headers, content): - """Modify the request headers to add the appropriate - Authorization header.""" - headers["authorization"] = "GoogleLogin Auth=" + self.Auth - - -AUTH_SCHEME_CLASSES = { - "basic": BasicAuthentication, - "wsse": WsseAuthentication, - "digest": DigestAuthentication, - "hmacdigest": HmacDigestAuthentication, - "googlelogin": GoogleLoginAuthentication, -} - -AUTH_SCHEME_ORDER = ["hmacdigest", "googlelogin", "digest", "wsse", "basic"] - - -class FileCache(object): - """Uses a local directory as a store for cached files. - Not really safe to use if multiple threads or processes are going to - be running on the same cache. - """ - - def __init__(self, cache, safe=safename): # use safe=lambda x: md5.new(x).hexdigest() for the old behavior - self.cache = cache - self.safe = safe - if not os.path.exists(cache): - os.makedirs(self.cache) - - def get(self, key): - retval = None - cacheFullPath = os.path.join(self.cache, self.safe(key)) - try: - f = open(cacheFullPath, "rb") - retval = f.read() - f.close() - except IOError: - pass - return retval - - def set(self, key, value): - cacheFullPath = os.path.join(self.cache, self.safe(key)) - f = open(cacheFullPath, "wb") - f.write(value) - f.close() - - def delete(self, key): - cacheFullPath = os.path.join(self.cache, self.safe(key)) - if os.path.exists(cacheFullPath): - os.remove(cacheFullPath) - - -class Credentials(object): - def __init__(self): - self.credentials = [] - - def add(self, name, password, domain=""): - self.credentials.append((domain.lower(), name, password)) - - def clear(self): - self.credentials = [] - - def iter(self, domain): - for (cdomain, name, password) in self.credentials: - if cdomain == "" or domain == cdomain: - yield (name, password) - - -class KeyCerts(Credentials): - """Identical to Credentials except that - name/password are mapped to key/cert.""" - - def add(self, key, cert, domain, password): - self.credentials.append((domain.lower(), key, cert, password)) - - def iter(self, domain): - for (cdomain, key, cert, password) in self.credentials: - if cdomain == "" or domain == cdomain: - yield (key, cert, password) - - -class AllHosts(object): - pass - - -class ProxyInfo(object): - """Collect information required to use a proxy.""" - - bypass_hosts = () - - def __init__( - self, proxy_type, proxy_host, proxy_port, proxy_rdns=True, proxy_user=None, proxy_pass=None, proxy_headers=None, - ): - """Args: - - proxy_type: The type of proxy server. This must be set to one of - socks.PROXY_TYPE_XXX constants. For example: p = - ProxyInfo(proxy_type=socks.PROXY_TYPE_HTTP, proxy_host='localhost', - proxy_port=8000) - proxy_host: The hostname or IP address of the proxy server. - proxy_port: The port that the proxy server is running on. - proxy_rdns: If True (default), DNS queries will not be performed - locally, and instead, handed to the proxy to resolve. This is useful - if the network does not allow resolution of non-local names. In - httplib2 0.9 and earlier, this defaulted to False. - proxy_user: The username used to authenticate with the proxy server. - proxy_pass: The password used to authenticate with the proxy server. - proxy_headers: Additional or modified headers for the proxy connect - request. - """ - if isinstance(proxy_user, bytes): - proxy_user = proxy_user.decode() - if isinstance(proxy_pass, bytes): - proxy_pass = proxy_pass.decode() - ( - self.proxy_type, - self.proxy_host, - self.proxy_port, - self.proxy_rdns, - self.proxy_user, - self.proxy_pass, - self.proxy_headers, - ) = ( - proxy_type, - proxy_host, - proxy_port, - proxy_rdns, - proxy_user, - proxy_pass, - proxy_headers, - ) - - def astuple(self): - return ( - self.proxy_type, - self.proxy_host, - self.proxy_port, - self.proxy_rdns, - self.proxy_user, - self.proxy_pass, - self.proxy_headers, - ) - - def isgood(self): - return socks and (self.proxy_host != None) and (self.proxy_port != None) - - def applies_to(self, hostname): - return not self.bypass_host(hostname) - - def bypass_host(self, hostname): - """Has this host been excluded from the proxy config""" - if self.bypass_hosts is AllHosts: - return True - - hostname = "." + hostname.lstrip(".") - for skip_name in self.bypass_hosts: - # *.suffix - if skip_name.startswith(".") and hostname.endswith(skip_name): - return True - # exact match - if hostname == "." + skip_name: - return True - return False - - def __repr__(self): - return ( - "" - ).format(p=self) - - -def proxy_info_from_environment(method="http"): - """Read proxy info from the environment variables. - """ - if method not in ("http", "https"): - return - - env_var = method + "_proxy" - url = os.environ.get(env_var, os.environ.get(env_var.upper())) - if not url: - return - return proxy_info_from_url(url, method, noproxy=None) - - -def proxy_info_from_url(url, method="http", noproxy=None): - """Construct a ProxyInfo from a URL (such as http_proxy env var) - """ - url = urllib.parse.urlparse(url) - - proxy_type = 3 # socks.PROXY_TYPE_HTTP - pi = ProxyInfo( - proxy_type=proxy_type, - proxy_host=url.hostname, - proxy_port=url.port or dict(https=443, http=80)[method], - proxy_user=url.username or None, - proxy_pass=url.password or None, - proxy_headers=None, - ) - - bypass_hosts = [] - # If not given an explicit noproxy value, respect values in env vars. - if noproxy is None: - noproxy = os.environ.get("no_proxy", os.environ.get("NO_PROXY", "")) - # Special case: A single '*' character means all hosts should be bypassed. - if noproxy == "*": - bypass_hosts = AllHosts - elif noproxy.strip(): - bypass_hosts = noproxy.split(",") - bypass_hosts = tuple(filter(bool, bypass_hosts)) # To exclude empty string. - - pi.bypass_hosts = bypass_hosts - return pi - - -class HTTPConnectionWithTimeout(http.client.HTTPConnection): - """HTTPConnection subclass that supports timeouts - - HTTPConnection subclass that supports timeouts - - All timeouts are in seconds. If None is passed for timeout then - Python's default timeout for sockets will be used. See for example - the docs of socket.setdefaulttimeout(): - http://docs.python.org/library/socket.html#socket.setdefaulttimeout - """ - - def __init__(self, host, port=None, timeout=None, proxy_info=None): - http.client.HTTPConnection.__init__(self, host, port=port, timeout=timeout) - - self.proxy_info = proxy_info - if proxy_info and not isinstance(proxy_info, ProxyInfo): - self.proxy_info = proxy_info("http") - - def connect(self): - """Connect to the host and port specified in __init__.""" - if self.proxy_info and socks is None: - raise ProxiesUnavailableError("Proxy support missing but proxy use was requested!") - if self.proxy_info and self.proxy_info.isgood() and self.proxy_info.applies_to(self.host): - use_proxy = True - ( - proxy_type, - proxy_host, - proxy_port, - proxy_rdns, - proxy_user, - proxy_pass, - proxy_headers, - ) = self.proxy_info.astuple() - - host = proxy_host - port = proxy_port - else: - use_proxy = False - - host = self.host - port = self.port - proxy_type = None - - socket_err = None - - for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM): - af, socktype, proto, canonname, sa = res - try: - if use_proxy: - self.sock = socks.socksocket(af, socktype, proto) - self.sock.setproxy( - proxy_type, proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, - ) - else: - self.sock = socket.socket(af, socktype, proto) - self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) - if has_timeout(self.timeout): - self.sock.settimeout(self.timeout) - if self.debuglevel > 0: - print("connect: ({0}, {1}) ************".format(self.host, self.port)) - if use_proxy: - print( - "proxy: {0} ************".format( - str((proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers,)) - ) - ) - - self.sock.connect((self.host, self.port) + sa[2:]) - except socket.error as e: - socket_err = e - if self.debuglevel > 0: - print("connect fail: ({0}, {1})".format(self.host, self.port)) - if use_proxy: - print( - "proxy: {0}".format( - str((proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers,)) - ) - ) - if self.sock: - self.sock.close() - self.sock = None - continue - break - if not self.sock: - raise socket_err - - -class HTTPSConnectionWithTimeout(http.client.HTTPSConnection): - """This class allows communication via SSL. - - All timeouts are in seconds. If None is passed for timeout then - Python's default timeout for sockets will be used. See for example - the docs of socket.setdefaulttimeout(): - http://docs.python.org/library/socket.html#socket.setdefaulttimeout - """ - - def __init__( - self, - host, - port=None, - key_file=None, - cert_file=None, - timeout=None, - proxy_info=None, - ca_certs=None, - disable_ssl_certificate_validation=False, - tls_maximum_version=None, - tls_minimum_version=None, - key_password=None, - ): - - self.disable_ssl_certificate_validation = disable_ssl_certificate_validation - self.ca_certs = ca_certs if ca_certs else CA_CERTS - - self.proxy_info = proxy_info - if proxy_info and not isinstance(proxy_info, ProxyInfo): - self.proxy_info = proxy_info("https") - - context = _build_ssl_context( - self.disable_ssl_certificate_validation, - self.ca_certs, - cert_file, - key_file, - maximum_version=tls_maximum_version, - minimum_version=tls_minimum_version, - key_password=key_password, - ) - super(HTTPSConnectionWithTimeout, self).__init__( - host, port=port, timeout=timeout, context=context, - ) - self.key_file = key_file - self.cert_file = cert_file - self.key_password = key_password - - def connect(self): - """Connect to a host on a given (SSL) port.""" - if self.proxy_info and self.proxy_info.isgood() and self.proxy_info.applies_to(self.host): - use_proxy = True - ( - proxy_type, - proxy_host, - proxy_port, - proxy_rdns, - proxy_user, - proxy_pass, - proxy_headers, - ) = self.proxy_info.astuple() - - host = proxy_host - port = proxy_port - else: - use_proxy = False - - host = self.host - port = self.port - proxy_type = None - proxy_headers = None - - socket_err = None - - address_info = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM) - for family, socktype, proto, canonname, sockaddr in address_info: - try: - if use_proxy: - sock = socks.socksocket(family, socktype, proto) - - sock.setproxy( - proxy_type, proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, - ) - else: - sock = socket.socket(family, socktype, proto) - sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) - if has_timeout(self.timeout): - sock.settimeout(self.timeout) - sock.connect((self.host, self.port)) - - self.sock = self._context.wrap_socket(sock, server_hostname=self.host) - - # Python 3.3 compatibility: emulate the check_hostname behavior - if not hasattr(self._context, "check_hostname") and not self.disable_ssl_certificate_validation: - try: - ssl.match_hostname(self.sock.getpeercert(), self.host) - except Exception: - self.sock.shutdown(socket.SHUT_RDWR) - self.sock.close() - raise - - if self.debuglevel > 0: - print("connect: ({0}, {1})".format(self.host, self.port)) - if use_proxy: - print( - "proxy: {0}".format( - str((proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers,)) - ) - ) - except (ssl.SSLError, ssl.CertificateError) as e: - if sock: - sock.close() - if self.sock: - self.sock.close() - self.sock = None - raise - except (socket.timeout, socket.gaierror): - raise - except socket.error as e: - socket_err = e - if self.debuglevel > 0: - print("connect fail: ({0}, {1})".format(self.host, self.port)) - if use_proxy: - print( - "proxy: {0}".format( - str((proxy_host, proxy_port, proxy_rdns, proxy_user, proxy_pass, proxy_headers,)) - ) - ) - if self.sock: - self.sock.close() - self.sock = None - continue - break - if not self.sock: - raise socket_err - - -SCHEME_TO_CONNECTION = { - "http": HTTPConnectionWithTimeout, - "https": HTTPSConnectionWithTimeout, -} - - -class Http(object): - """An HTTP client that handles: - - - all methods - - caching - - ETags - - compression, - - HTTPS - - Basic - - Digest - - WSSE - - and more. - """ - - def __init__( - self, - cache=None, - timeout=None, - proxy_info=proxy_info_from_environment, - ca_certs=None, - disable_ssl_certificate_validation=False, - tls_maximum_version=None, - tls_minimum_version=None, - ): - """If 'cache' is a string then it is used as a directory name for - a disk cache. Otherwise it must be an object that supports the - same interface as FileCache. - - All timeouts are in seconds. If None is passed for timeout - then Python's default timeout for sockets will be used. See - for example the docs of socket.setdefaulttimeout(): - http://docs.python.org/library/socket.html#socket.setdefaulttimeout - - `proxy_info` may be: - - a callable that takes the http scheme ('http' or 'https') and - returns a ProxyInfo instance per request. By default, uses - proxy_info_from_environment. - - a ProxyInfo instance (static proxy config). - - None (proxy disabled). - - ca_certs is the path of a file containing root CA certificates for SSL - server certificate validation. By default, a CA cert file bundled with - httplib2 is used. - - If disable_ssl_certificate_validation is true, SSL cert validation will - not be performed. - - tls_maximum_version / tls_minimum_version require Python 3.7+ / - OpenSSL 1.1.0g+. A value of "TLSv1_3" requires OpenSSL 1.1.1+. - """ - self.proxy_info = proxy_info - self.ca_certs = ca_certs - self.disable_ssl_certificate_validation = disable_ssl_certificate_validation - self.tls_maximum_version = tls_maximum_version - self.tls_minimum_version = tls_minimum_version - # Map domain name to an httplib connection - self.connections = {} - # The location of the cache, for now a directory - # where cached responses are held. - if cache and isinstance(cache, str): - self.cache = FileCache(cache) - else: - self.cache = cache - - # Name/password - self.credentials = Credentials() - - # Key/cert - self.certificates = KeyCerts() - - # authorization objects - self.authorizations = [] - - # If set to False then no redirects are followed, even safe ones. - self.follow_redirects = True - - self.redirect_codes = REDIRECT_CODES - - # Which HTTP methods do we apply optimistic concurrency to, i.e. - # which methods get an "if-match:" etag header added to them. - self.optimistic_concurrency_methods = ["PUT", "PATCH"] - - self.safe_methods = list(SAFE_METHODS) - - # If 'follow_redirects' is True, and this is set to True then - # all redirecs are followed, including unsafe ones. - self.follow_all_redirects = False - - self.ignore_etag = False - - self.force_exception_to_status_code = False - - self.timeout = timeout - - # Keep Authorization: headers on a redirect. - self.forward_authorization_headers = False - - def close(self): - """Close persistent connections, clear sensitive data. - Not thread-safe, requires external synchronization against concurrent requests. - """ - existing, self.connections = self.connections, {} - for _, c in existing.items(): - c.close() - self.certificates.clear() - self.clear_credentials() - - def __getstate__(self): - state_dict = copy.copy(self.__dict__) - # In case request is augmented by some foreign object such as - # credentials which handle auth - if "request" in state_dict: - del state_dict["request"] - if "connections" in state_dict: - del state_dict["connections"] - return state_dict - - def __setstate__(self, state): - self.__dict__.update(state) - self.connections = {} - - def _auth_from_challenge(self, host, request_uri, headers, response, content): - """A generator that creates Authorization objects - that can be applied to requests. - """ - challenges = auth._parse_www_authenticate(response, "www-authenticate") - for cred in self.credentials.iter(host): - for scheme in AUTH_SCHEME_ORDER: - if scheme in challenges: - yield AUTH_SCHEME_CLASSES[scheme](cred, host, request_uri, headers, response, content, self) - - def add_credentials(self, name, password, domain=""): - """Add a name and password that will be used - any time a request requires authentication.""" - self.credentials.add(name, password, domain) - - def add_certificate(self, key, cert, domain, password=None): - """Add a key and cert that will be used - any time a request requires authentication.""" - self.certificates.add(key, cert, domain, password) - - def clear_credentials(self): - """Remove all the names and passwords - that are used for authentication""" - self.credentials.clear() - self.authorizations = [] - - def _conn_request(self, conn, request_uri, method, body, headers): - i = 0 - seen_bad_status_line = False - while i < RETRIES: - i += 1 - try: - if conn.sock is None: - conn.connect() - conn.request(method, request_uri, body, headers) - except socket.timeout: - conn.close() - raise - except socket.gaierror: - conn.close() - raise ServerNotFoundError("Unable to find the server at %s" % conn.host) - except socket.error as e: - errno_ = _errno_from_exception(e) - if errno_ in (errno.ENETUNREACH, errno.EADDRNOTAVAIL) and i < RETRIES: - continue # retry on potentially transient errors - raise - except http.client.HTTPException: - if conn.sock is None: - if i < RETRIES - 1: - conn.close() - conn.connect() - continue - else: - conn.close() - raise - if i < RETRIES - 1: - conn.close() - conn.connect() - continue - # Just because the server closed the connection doesn't apparently mean - # that the server didn't send a response. - pass - try: - response = conn.getresponse() - except (http.client.BadStatusLine, http.client.ResponseNotReady): - # If we get a BadStatusLine on the first try then that means - # the connection just went stale, so retry regardless of the - # number of RETRIES set. - if not seen_bad_status_line and i == 1: - i = 0 - seen_bad_status_line = True - conn.close() - conn.connect() - continue - else: - conn.close() - raise - except socket.timeout: - raise - except (socket.error, http.client.HTTPException): - conn.close() - if i == 0: - conn.close() - conn.connect() - continue - else: - raise - else: - content = b"" - if method == "HEAD": - conn.close() - else: - content = response.read() - response = Response(response) - if method != "HEAD": - content = _decompressContent(response, content) - - break - return (response, content) - - def _request( - self, conn, host, absolute_uri, request_uri, method, body, headers, redirections, cachekey, - ): - """Do the actual request using the connection object - and also follow one level of redirects if necessary""" - - auths = [(auth.depth(request_uri), auth) for auth in self.authorizations if auth.inscope(host, request_uri)] - auth = auths and sorted(auths)[0][1] or None - if auth: - auth.request(method, request_uri, headers, body) - - (response, content) = self._conn_request(conn, request_uri, method, body, headers) - - if auth: - if auth.response(response, body): - auth.request(method, request_uri, headers, body) - (response, content) = self._conn_request(conn, request_uri, method, body, headers) - response._stale_digest = 1 - - if response.status == 401: - for authorization in self._auth_from_challenge(host, request_uri, headers, response, content): - authorization.request(method, request_uri, headers, body) - (response, content) = self._conn_request(conn, request_uri, method, body, headers) - if response.status != 401: - self.authorizations.append(authorization) - authorization.response(response, body) - break - - if self.follow_all_redirects or method in self.safe_methods or response.status in (303, 308): - if self.follow_redirects and response.status in self.redirect_codes: - # Pick out the location header and basically start from the beginning - # remembering first to strip the ETag header and decrement our 'depth' - if redirections: - if "location" not in response and response.status != 300: - raise RedirectMissingLocation( - _("Redirected but the response is missing a Location: header."), response, content, - ) - # Fix-up relative redirects (which violate an RFC 2616 MUST) - if "location" in response: - location = response["location"] - (scheme, authority, path, query, fragment) = parse_uri(location) - if authority == None: - response["location"] = urllib.parse.urljoin(absolute_uri, location) - if response.status == 308 or (response.status == 301 and (method in self.safe_methods)): - response["-x-permanent-redirect-url"] = response["location"] - if "content-location" not in response: - response["content-location"] = absolute_uri - _updateCache(headers, response, content, self.cache, cachekey) - if "if-none-match" in headers: - del headers["if-none-match"] - if "if-modified-since" in headers: - del headers["if-modified-since"] - if "authorization" in headers and not self.forward_authorization_headers: - del headers["authorization"] - if "location" in response: - location = response["location"] - old_response = copy.deepcopy(response) - if "content-location" not in old_response: - old_response["content-location"] = absolute_uri - redirect_method = method - if response.status in [302, 303]: - redirect_method = "GET" - body = None - (response, content) = self.request( - location, method=redirect_method, body=body, headers=headers, redirections=redirections - 1, - ) - response.previous = old_response - else: - raise RedirectLimit( - "Redirected more times than redirection_limit allows.", response, content, - ) - elif response.status in [200, 203] and method in self.safe_methods: - # Don't cache 206's since we aren't going to handle byte range requests - if "content-location" not in response: - response["content-location"] = absolute_uri - _updateCache(headers, response, content, self.cache, cachekey) - - return (response, content) - - def _normalize_headers(self, headers): - return _normalize_headers(headers) - - # Need to catch and rebrand some exceptions - # Then need to optionally turn all exceptions into status codes - # including all socket.* and httplib.* exceptions. - - def request( - self, uri, method="GET", body=None, headers=None, redirections=DEFAULT_MAX_REDIRECTS, connection_type=None, - ): - """ Performs a single HTTP request. -The 'uri' is the URI of the HTTP resource and can begin -with either 'http' or 'https'. The value of 'uri' must be an absolute URI. - -The 'method' is the HTTP method to perform, such as GET, POST, DELETE, etc. -There is no restriction on the methods allowed. - -The 'body' is the entity body to be sent with the request. It is a string -object. - -Any extra headers that are to be sent with the request should be provided in the -'headers' dictionary. - -The maximum number of redirect to follow before raising an -exception is 'redirections. The default is 5. - -The return value is a tuple of (response, content), the first -being and instance of the 'Response' class, the second being -a string that contains the response entity body. - """ - conn_key = "" - - try: - if headers is None: - headers = {} - else: - headers = self._normalize_headers(headers) - - if "user-agent" not in headers: - headers["user-agent"] = "Python-httplib2/%s (gzip)" % __version__ - - uri = iri2uri(uri) - # Prevent CWE-75 space injection to manipulate request via part of uri. - # Prevent CWE-93 CRLF injection to modify headers via part of uri. - uri = uri.replace(" ", "%20").replace("\r", "%0D").replace("\n", "%0A") - - (scheme, authority, request_uri, defrag_uri) = urlnorm(uri) - - conn_key = scheme + ":" + authority - conn = self.connections.get(conn_key) - if conn is None: - if not connection_type: - connection_type = SCHEME_TO_CONNECTION[scheme] - certs = list(self.certificates.iter(authority)) - if issubclass(connection_type, HTTPSConnectionWithTimeout): - if certs: - conn = self.connections[conn_key] = connection_type( - authority, - key_file=certs[0][0], - cert_file=certs[0][1], - timeout=self.timeout, - proxy_info=self.proxy_info, - ca_certs=self.ca_certs, - disable_ssl_certificate_validation=self.disable_ssl_certificate_validation, - tls_maximum_version=self.tls_maximum_version, - tls_minimum_version=self.tls_minimum_version, - key_password=certs[0][2], - ) - else: - conn = self.connections[conn_key] = connection_type( - authority, - timeout=self.timeout, - proxy_info=self.proxy_info, - ca_certs=self.ca_certs, - disable_ssl_certificate_validation=self.disable_ssl_certificate_validation, - tls_maximum_version=self.tls_maximum_version, - tls_minimum_version=self.tls_minimum_version, - ) - else: - conn = self.connections[conn_key] = connection_type( - authority, timeout=self.timeout, proxy_info=self.proxy_info - ) - conn.set_debuglevel(debuglevel) - - if "range" not in headers and "accept-encoding" not in headers: - headers["accept-encoding"] = "gzip, deflate" - - info = email.message.Message() - cachekey = None - cached_value = None - if self.cache: - cachekey = defrag_uri - cached_value = self.cache.get(cachekey) - if cached_value: - try: - info, content = cached_value.split(b"\r\n\r\n", 1) - info = email.message_from_bytes(info) - for k, v in info.items(): - if v.startswith("=?") and v.endswith("?="): - info.replace_header(k, str(*email.header.decode_header(v)[0])) - except (IndexError, ValueError): - self.cache.delete(cachekey) - cachekey = None - cached_value = None - - if ( - method in self.optimistic_concurrency_methods - and self.cache - and "etag" in info - and not self.ignore_etag - and "if-match" not in headers - ): - # http://www.w3.org/1999/04/Editing/ - headers["if-match"] = info["etag"] - - # https://tools.ietf.org/html/rfc7234 - # A cache MUST invalidate the effective Request URI as well as [...] Location and Content-Location - # when a non-error status code is received in response to an unsafe request method. - if self.cache and cachekey and method not in self.safe_methods: - self.cache.delete(cachekey) - - # Check the vary header in the cache to see if this request - # matches what varies in the cache. - if method in self.safe_methods and "vary" in info: - vary = info["vary"] - vary_headers = vary.lower().replace(" ", "").split(",") - for header in vary_headers: - key = "-varied-%s" % header - value = info[key] - if headers.get(header, None) != value: - cached_value = None - break - - if ( - self.cache - and cached_value - and (method in self.safe_methods or info["status"] == "308") - and "range" not in headers - ): - redirect_method = method - if info["status"] not in ("307", "308"): - redirect_method = "GET" - if "-x-permanent-redirect-url" in info: - # Should cached permanent redirects be counted in our redirection count? For now, yes. - if redirections <= 0: - raise RedirectLimit( - "Redirected more times than redirection_limit allows.", {}, "", - ) - (response, new_content) = self.request( - info["-x-permanent-redirect-url"], - method=redirect_method, - headers=headers, - redirections=redirections - 1, - ) - response.previous = Response(info) - response.previous.fromcache = True - else: - # Determine our course of action: - # Is the cached entry fresh or stale? - # Has the client requested a non-cached response? - # - # There seems to be three possible answers: - # 1. [FRESH] Return the cache entry w/o doing a GET - # 2. [STALE] Do the GET (but add in cache validators if available) - # 3. [TRANSPARENT] Do a GET w/o any cache validators (Cache-Control: no-cache) on the request - entry_disposition = _entry_disposition(info, headers) - - if entry_disposition == "FRESH": - response = Response(info) - response.fromcache = True - return (response, content) - - if entry_disposition == "STALE": - if "etag" in info and not self.ignore_etag and not "if-none-match" in headers: - headers["if-none-match"] = info["etag"] - if "last-modified" in info and not "last-modified" in headers: - headers["if-modified-since"] = info["last-modified"] - elif entry_disposition == "TRANSPARENT": - pass - - (response, new_content) = self._request( - conn, authority, uri, request_uri, method, body, headers, redirections, cachekey, - ) - - if response.status == 304 and method == "GET": - # Rewrite the cache entry with the new end-to-end headers - # Take all headers that are in response - # and overwrite their values in info. - # unless they are hop-by-hop, or are listed in the connection header. - - for key in _get_end2end_headers(response): - info[key] = response[key] - merged_response = Response(info) - if hasattr(response, "_stale_digest"): - merged_response._stale_digest = response._stale_digest - _updateCache(headers, merged_response, content, self.cache, cachekey) - response = merged_response - response.status = 200 - response.fromcache = True - - elif response.status == 200: - content = new_content - else: - self.cache.delete(cachekey) - content = new_content - else: - cc = _parse_cache_control(headers) - if "only-if-cached" in cc: - info["status"] = "504" - response = Response(info) - content = b"" - else: - (response, content) = self._request( - conn, authority, uri, request_uri, method, body, headers, redirections, cachekey, - ) - except Exception as e: - is_timeout = isinstance(e, socket.timeout) - if is_timeout: - conn = self.connections.pop(conn_key, None) - if conn: - conn.close() - - if self.force_exception_to_status_code: - if isinstance(e, HttpLib2ErrorWithResponse): - response = e.response - content = e.content - response.status = 500 - response.reason = str(e) - elif isinstance(e, socket.timeout): - content = b"Request Timeout" - response = Response({"content-type": "text/plain", "status": "408", "content-length": len(content),}) - response.reason = "Request Timeout" - else: - content = str(e).encode("utf-8") - response = Response({"content-type": "text/plain", "status": "400", "content-length": len(content),}) - response.reason = "Bad Request" - else: - raise - - return (response, content) - - -class Response(dict): - """An object more like email.message than httplib.HTTPResponse.""" - - """Is this response from our local cache""" - fromcache = False - """HTTP protocol version used by server. - - 10 for HTTP/1.0, 11 for HTTP/1.1. - """ - version = 11 - - "Status code returned by server. " - status = 200 - """Reason phrase returned by server.""" - reason = "Ok" - - previous = None - - def __init__(self, info): - # info is either an email.message or - # an httplib.HTTPResponse object. - if isinstance(info, http.client.HTTPResponse): - for key, value in info.getheaders(): - key = key.lower() - prev = self.get(key) - if prev is not None: - value = ", ".join((prev, value)) - self[key] = value - self.status = info.status - self["status"] = str(self.status) - self.reason = info.reason - self.version = info.version - elif isinstance(info, email.message.Message): - for key, value in list(info.items()): - self[key.lower()] = value - self.status = int(self["status"]) - else: - for key, value in info.items(): - self[key.lower()] = value - self.status = int(self.get("status", self.status)) - - def __getattr__(self, name): - if name == "dict": - return self - else: - raise AttributeError(name) diff --git a/shotgun_api3/lib/httplib2/python3/auth.py b/shotgun_api3/lib/httplib2/python3/auth.py deleted file mode 100644 index 53f427be1..000000000 --- a/shotgun_api3/lib/httplib2/python3/auth.py +++ /dev/null @@ -1,69 +0,0 @@ -import base64 -import re - -from ... import pyparsing as pp - -from .error import * - - -try: # pyparsing>=3.0.0 - downcaseTokens = pp.common.downcaseTokens -except AttributeError: - downcaseTokens = pp.downcaseTokens - -UNQUOTE_PAIRS = re.compile(r"\\(.)") -unquote = lambda s, l, t: UNQUOTE_PAIRS.sub(r"\1", t[0][1:-1]) - -# https://tools.ietf.org/html/rfc7235#section-1.2 -# https://tools.ietf.org/html/rfc7235#appendix-B -tchar = "!#$%&'*+-.^_`|~" + pp.nums + pp.alphas -token = pp.Word(tchar).setName("token") -token68 = pp.Combine(pp.Word("-._~+/" + pp.nums + pp.alphas) + pp.Optional(pp.Word("=").leaveWhitespace())).setName( - "token68" -) - -quoted_string = pp.dblQuotedString.copy().setName("quoted-string").setParseAction(unquote) -auth_param_name = token.copy().setName("auth-param-name").addParseAction(downcaseTokens) -auth_param = auth_param_name + pp.Suppress("=") + (quoted_string | token) -params = pp.Dict(pp.delimitedList(pp.Group(auth_param))) - -scheme = token("scheme") -challenge = scheme + (params("params") | token68("token")) - -authentication_info = params.copy() -www_authenticate = pp.delimitedList(pp.Group(challenge)) - - -def _parse_authentication_info(headers, headername="authentication-info"): - """https://tools.ietf.org/html/rfc7615 - """ - header = headers.get(headername, "").strip() - if not header: - return {} - try: - parsed = authentication_info.parseString(header) - except pp.ParseException as ex: - # print(ex.explain(ex)) - raise MalformedHeader(headername) - - return parsed.asDict() - - -def _parse_www_authenticate(headers, headername="www-authenticate"): - """Returns a dictionary of dictionaries, one dict per auth_scheme.""" - header = headers.get(headername, "").strip() - if not header: - return {} - try: - parsed = www_authenticate.parseString(header) - except pp.ParseException as ex: - # print(ex.explain(ex)) - raise MalformedHeader(headername) - - retval = { - challenge["scheme"].lower(): challenge["params"].asDict() - if "params" in challenge - else {"token": challenge.get("token")} - for challenge in parsed - } - return retval diff --git a/shotgun_api3/lib/httplib2/python3/cacerts.txt b/shotgun_api3/lib/httplib2/python3/cacerts.txt deleted file mode 100644 index 78a444c43..000000000 --- a/shotgun_api3/lib/httplib2/python3/cacerts.txt +++ /dev/null @@ -1,2225 +0,0 @@ -# Issuer: CN=GTE CyberTrust Global Root O=GTE Corporation OU=GTE CyberTrust Solutions, Inc. -# Subject: CN=GTE CyberTrust Global Root O=GTE Corporation OU=GTE CyberTrust Solutions, Inc. -# Label: "GTE CyberTrust Global Root" -# Serial: 421 -# MD5 Fingerprint: ca:3d:d3:68:f1:03:5c:d0:32:fa:b8:2b:59:e8:5a:db -# SHA1 Fingerprint: 97:81:79:50:d8:1c:96:70:cc:34:d8:09:cf:79:44:31:36:7e:f4:74 -# SHA256 Fingerprint: a5:31:25:18:8d:21:10:aa:96:4b:02:c7:b7:c6:da:32:03:17:08:94:e5:fb:71:ff:fb:66:67:d5:e6:81:0a:36 ------BEGIN CERTIFICATE----- -MIICWjCCAcMCAgGlMA0GCSqGSIb3DQEBBAUAMHUxCzAJBgNVBAYTAlVTMRgwFgYD -VQQKEw9HVEUgQ29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNv -bHV0aW9ucywgSW5jLjEjMCEGA1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJv -b3QwHhcNOTgwODEzMDAyOTAwWhcNMTgwODEzMjM1OTAwWjB1MQswCQYDVQQGEwJV -UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU -cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds -b2JhbCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVD6C28FCc6HrH -iM3dFw4usJTQGz0O9pTAipTHBsiQl8i4ZBp6fmw8U+E3KHNgf7KXUwefU/ltWJTS -r41tiGeA5u2ylc9yMcqlHHK6XALnZELn+aks1joNrI1CqiQBOeacPwGFVw1Yh0X4 -04Wqk2kmhXBIgD8SFcd5tB8FLztimQIDAQABMA0GCSqGSIb3DQEBBAUAA4GBAG3r -GwnpXtlR22ciYaQqPEh346B8pt5zohQDhT37qw4wxYMWM4ETCJ57NE7fQMh017l9 -3PR2VX2bY1QY6fDq81yx2YtCHrnAlU66+tXifPVoYb+O7AWXX1uw16OFNMQkpw0P -lZPvy5TYnh+dXIVtx6quTx8itc2VrbqnzPmrC3p/ ------END CERTIFICATE----- - -# Issuer: CN=Thawte Server CA O=Thawte Consulting cc OU=Certification Services Division -# Subject: CN=Thawte Server CA O=Thawte Consulting cc OU=Certification Services Division -# Label: "Thawte Server CA" -# Serial: 1 -# MD5 Fingerprint: c5:70:c4:a2:ed:53:78:0c:c8:10:53:81:64:cb:d0:1d -# SHA1 Fingerprint: 23:e5:94:94:51:95:f2:41:48:03:b4:d5:64:d2:a3:a3:f5:d8:8b:8c -# SHA256 Fingerprint: b4:41:0b:73:e2:e6:ea:ca:47:fb:c4:2f:8f:a4:01:8a:f4:38:1d:c5:4c:fa:a8:44:50:46:1e:ed:09:45:4d:e9 ------BEGIN CERTIFICATE----- -MIIDEzCCAnygAwIBAgIBATANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMCWkEx -FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD -VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv -biBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEm -MCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wHhcNOTYwODAx -MDAwMDAwWhcNMjAxMjMxMjM1OTU5WjCBxDELMAkGA1UEBhMCWkExFTATBgNVBAgT -DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3 -dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNl -cyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3 -DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQAD -gY0AMIGJAoGBANOkUG7I/1Zr5s9dtuoMaHVHoqrC2oQl/Kj0R1HahbUgdJSGHg91 -yekIYfUGbTBuFRkC6VLAYttNmZ7iagxEOM3+vuNkCXDF/rFrKbYvScg71CcEJRCX -L+eQbcAoQpnXTEPew/UhbVSfXcNY4cDk2VuwuNy0e982OsK1ZiIS1ocNAgMBAAGj -EzARMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAB/pMaVz7lcxG -7oWDTSEwjsrZqG9JGubaUeNgcGyEYRGhGshIPllDfU+VPaGLtwtimHp1it2ITk6e -QNuozDJ0uW8NxuOzRAvZim+aKZuZGCg70eNAKJpaPNW15yAbi8qkq43pUdniTCxZ -qdq5snUb9kLy78fyGPmJvKP/iiMucEc= ------END CERTIFICATE----- - -# Issuer: CN=Thawte Premium Server CA O=Thawte Consulting cc OU=Certification Services Division -# Subject: CN=Thawte Premium Server CA O=Thawte Consulting cc OU=Certification Services Division -# Label: "Thawte Premium Server CA" -# Serial: 1 -# MD5 Fingerprint: 06:9f:69:79:16:66:90:02:1b:8c:8c:a2:c3:07:6f:3a -# SHA1 Fingerprint: 62:7f:8d:78:27:65:63:99:d2:7d:7f:90:44:c9:fe:b3:f3:3e:fa:9a -# SHA256 Fingerprint: ab:70:36:36:5c:71:54:aa:29:c2:c2:9f:5d:41:91:16:3b:16:2a:22:25:01:13:57:d5:6d:07:ff:a7:bc:1f:72 ------BEGIN CERTIFICATE----- -MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMCWkEx -FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD -VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv -biBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhhd3RlIFByZW1pdW0gU2Vy -dmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNlcnZlckB0aGF3dGUuY29t -MB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgc4xCzAJBgNVBAYTAlpB -MRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEdMBsG -A1UEChMUVGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNVBAsTH0NlcnRpZmljYXRp -b24gU2VydmljZXMgRGl2aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNl -cnZlciBDQTEoMCYGCSqGSIb3DQEJARYZcHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNv -bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2aovXwlue2oFBYo847kkE -VdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560ZXUCTe/LCaIhUdib0GfQ -ug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j+ao6hnO2RlNYyIkFvYMR -uHM/qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG -9w0BAQQFAAOBgQAmSCwWwlj66BZ0DKqqX1Q/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUI -hfzJATj/Tb7yFkJD57taRvvBxhEf8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZa4JM -pAwSremkrj/xw0llmozFyD4lt5SZu5IycQfwhl7tUCemDaYj+bvLpgcUQg== ------END CERTIFICATE----- - -# Issuer: O=Equifax OU=Equifax Secure Certificate Authority -# Subject: O=Equifax OU=Equifax Secure Certificate Authority -# Label: "Equifax Secure CA" -# Serial: 903804111 -# MD5 Fingerprint: 67:cb:9d:c0:13:24:8a:82:9b:b2:17:1e:d1:1b:ec:d4 -# SHA1 Fingerprint: d2:32:09:ad:23:d3:14:23:21:74:e4:0d:7f:9d:62:13:97:86:63:3a -# SHA256 Fingerprint: 08:29:7a:40:47:db:a2:36:80:c7:31:db:6e:31:76:53:ca:78:48:e1:be:bd:3a:0b:01:79:a7:07:f9:2c:f1:78 ------BEGIN CERTIFICATE----- -MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV -UzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2Vy -dGlmaWNhdGUgQXV0aG9yaXR5MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1 -MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoTB0VxdWlmYXgxLTArBgNVBAsTJEVx -dWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCBnzANBgkqhkiG9w0B -AQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPRfM6f -BeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+A -cJkVV5MW8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kC -AwEAAaOCAQkwggEFMHAGA1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQ -MA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlm -aWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTgw -ODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvSspXXR9gj -IBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQF -MAMBAf8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUA -A4GBAFjOKer89961zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y -7qj/WsjTVbJmcVfewCHrPSqnI0kBBIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh -1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee9570+sB3c4 ------END CERTIFICATE----- - -# Issuer: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority - G2/(c) 1998 VeriSign, Inc. - For authorized use only/VeriSign Trust Network -# Subject: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority - G2/(c) 1998 VeriSign, Inc. - For authorized use only/VeriSign Trust Network -# Label: "Verisign Class 3 Public Primary Certification Authority - G2" -# Serial: 167285380242319648451154478808036881606 -# MD5 Fingerprint: a2:33:9b:4c:74:78:73:d4:6c:e7:c1:f3:8d:cb:5c:e9 -# SHA1 Fingerprint: 85:37:1c:a6:e5:50:14:3d:ce:28:03:47:1b:de:3a:09:e8:f8:77:0f -# SHA256 Fingerprint: 83:ce:3c:12:29:68:8a:59:3d:48:5f:81:97:3c:0f:91:95:43:1e:da:37:cc:5e:36:43:0e:79:c7:a8:88:63:8b ------BEGIN CERTIFICATE----- -MIIDAjCCAmsCEH3Z/gfPqB63EHln+6eJNMYwDQYJKoZIhvcNAQEFBQAwgcExCzAJ -BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xh -c3MgMyBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcy -MTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3Jp -emVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMB4X -DTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVTMRcw -FQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMg -UHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEo -YykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5 -MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEB -AQUAA4GNADCBiQKBgQDMXtERXVxp0KvTuWpMmR9ZmDCOFoUgRm1HP9SFIIThbbP4 -pO0M8RcPO/mn+SXXwc+EY/J8Y8+iR/LGWzOOZEAEaMGAuWQcRXfH2G71lSk8UOg0 -13gfqLptQ5GVj0VXXn7F+8qkBOvqlzdUMG+7AUcyM83cV5tkaWH4mx0ciU9cZwID -AQABMA0GCSqGSIb3DQEBBQUAA4GBAFFNzb5cy5gZnBWyATl4Lk0PZ3BwmcYQWpSk -U01UbSuvDV1Ai2TT1+7eVmGSX6bEHRBhNtMsJzzoKQm5EWR0zLVznxxIqbxhAe7i -F6YM40AIOw7n60RzKprxaZLvcRTDOaxxp5EJb+RxBrO6WVcmeQD2+A2iMzAo1KpY -oJ2daZH9 ------END CERTIFICATE----- - -# Issuer: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA -# Subject: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA -# Label: "GlobalSign Root CA" -# Serial: 4835703278459707669005204 -# MD5 Fingerprint: 3e:45:52:15:09:51:92:e1:b7:5d:37:9f:b1:87:29:8a -# SHA1 Fingerprint: b1:bc:96:8b:d4:f4:9d:62:2a:a8:9a:81:f2:15:01:52:a4:1d:82:9c -# SHA256 Fingerprint: eb:d4:10:40:e4:bb:3e:c7:42:c9:e3:81:d3:1e:f2:a4:1a:48:b6:68:5c:96:e7:ce:f3:c1:df:6c:d4:33:1c:99 ------BEGIN CERTIFICATE----- -MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG -A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv -b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw -MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i -YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT -aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ -jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp -xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp -1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG -snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ -U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8 -9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E -BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B -AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz -yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE -38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP -AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad -DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME -HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== ------END CERTIFICATE----- - -# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R2 -# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R2 -# Label: "GlobalSign Root CA - R2" -# Serial: 4835703278459682885658125 -# MD5 Fingerprint: 94:14:77:7e:3e:5e:fd:8f:30:bd:41:b0:cf:e7:d0:30 -# SHA1 Fingerprint: 75:e0:ab:b6:13:85:12:27:1c:04:f8:5f:dd:de:38:e4:b7:24:2e:fe -# SHA256 Fingerprint: ca:42:dd:41:74:5f:d0:b8:1e:b9:02:36:2c:f9:d8:bf:71:9d:a1:bd:1b:1e:fc:94:6f:5b:4c:99:f4:2c:1b:9e ------BEGIN CERTIFICATE----- -MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4G -A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNp -Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1 -MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEG -A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI -hvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6ErPL -v4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8 -eoLrvozps6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklq -tTleiDTsvHgMCJiEbKjNS7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzd -C9XZzPnqJworc5HGnRusyMvo4KD0L5CLTfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pa -zq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6CygPCm48CAwEAAaOBnDCB -mTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUm+IH -V2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5n -bG9iYWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG -3lm0mi3f3BmGLjANBgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4Gs -J0/WwbgcQ3izDJr86iw8bmEbTUsp9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO -291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu01yiPqFbQfXf5WRDLenVOavS -ot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG79G+dwfCMNYxd -AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7 -TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg== ------END CERTIFICATE----- - -# Issuer: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 1 Policy Validation Authority -# Subject: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 1 Policy Validation Authority -# Label: "ValiCert Class 1 VA" -# Serial: 1 -# MD5 Fingerprint: 65:58:ab:15:ad:57:6c:1e:a8:a7:b5:69:ac:bf:ff:eb -# SHA1 Fingerprint: e5:df:74:3c:b6:01:c4:9b:98:43:dc:ab:8c:e8:6a:81:10:9f:e4:8e -# SHA256 Fingerprint: f4:c1:49:55:1a:30:13:a3:5b:c7:bf:fe:17:a7:f3:44:9b:c1:ab:5b:5a:0a:e7:4b:06:c2:3b:90:00:4c:01:04 ------BEGIN CERTIFICATE----- -MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0 -IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz -BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y -aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG -9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNTIyMjM0OFoXDTE5MDYy -NTIyMjM0OFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y -azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs -YXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw -Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl -cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYWYJ6ibiWuqYvaG9Y -LqdUHAZu9OqNSLwxlBfw8068srg1knaw0KWlAdcAAxIiGQj4/xEjm84H9b9pGib+ -TunRf50sQB1ZaG6m+FiwnRqP0z/x3BkGgagO4DrdyFNFCQbmD3DD+kCmDuJWBQ8Y -TfwggtFzVXSNdnKgHZ0dwN0/cQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFBoPUn0 -LBwGlN+VYH+Wexf+T3GtZMjdd9LvWVXoP+iOBSoh8gfStadS/pyxtuJbdxdA6nLW -I8sogTLDAHkY7FkXicnGah5xyf23dKUlRWnFSKsZ4UWKJWsZ7uW7EvV/96aNUcPw -nXS3qT6gpf+2SQMT2iLM7XGCK5nPOrf1LXLI ------END CERTIFICATE----- - -# Issuer: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 2 Policy Validation Authority -# Subject: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 2 Policy Validation Authority -# Label: "ValiCert Class 2 VA" -# Serial: 1 -# MD5 Fingerprint: a9:23:75:9b:ba:49:36:6e:31:c2:db:f2:e7:66:ba:87 -# SHA1 Fingerprint: 31:7a:2a:d0:7f:2b:33:5e:f5:a1:c3:4e:4b:57:e8:b7:d8:f1:fc:a6 -# SHA256 Fingerprint: 58:d0:17:27:9c:d4:dc:63:ab:dd:b1:96:a6:c9:90:6c:30:c4:e0:87:83:ea:e8:c1:60:99:54:d6:93:55:59:6b ------BEGIN CERTIFICATE----- -MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0 -IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz -BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y -aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG -9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMTk1NFoXDTE5MDYy -NjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y -azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs -YXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw -Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl -cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOOnHK5avIWZJV16vY -dA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVCCSRrCl6zfN1SLUzm1NZ9 -WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7RfZHM047QS -v4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9v -UJSZSWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTu -IYEZoDJJKPTEjlbVUjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwC -W/POuZ6lcg5Ktz885hZo+L7tdEy8W9ViH0Pd ------END CERTIFICATE----- - -# Issuer: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 3 Policy Validation Authority -# Subject: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 3 Policy Validation Authority -# Label: "RSA Root Certificate 1" -# Serial: 1 -# MD5 Fingerprint: a2:6f:53:b7:ee:40:db:4a:68:e7:fa:18:d9:10:4b:72 -# SHA1 Fingerprint: 69:bd:8c:f4:9c:d3:00:fb:59:2e:17:93:ca:55:6a:f3:ec:aa:35:fb -# SHA256 Fingerprint: bc:23:f9:8a:31:3c:b9:2d:e3:bb:fc:3a:5a:9f:44:61:ac:39:49:4c:4a:e1:5a:9e:9d:f1:31:e9:9b:73:01:9a ------BEGIN CERTIFICATE----- -MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0 -IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz -BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y -aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG -9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMjIzM1oXDTE5MDYy -NjAwMjIzM1owgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y -azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs -YXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw -Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl -cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDjmFGWHOjVsQaBalfD -cnWTq8+epvzzFlLWLU2fNUSoLgRNB0mKOCn1dzfnt6td3zZxFJmP3MKS8edgkpfs -2Ejcv8ECIMYkpChMMFp2bbFc893enhBxoYjHW5tBbcqwuI4V7q0zK89HBFx1cQqY -JJgpp0lZpd34t0NiYfPT4tBVPwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFa7AliE -Zwgs3x/be0kz9dNnnfS0ChCzycUs4pJqcXgn8nCDQtM+z6lU9PHYkhaM0QTLS6vJ -n0WuPIqpsHEzXcjFV9+vqDWzf4mH6eglkrh/hXqu1rweN1gqZ8mRzyqBPu3GOd/A -PhmcGcwTTYJBtYze4D1gCCAPRX5ron+jjBXu ------END CERTIFICATE----- - -# Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only -# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only -# Label: "Verisign Class 3 Public Primary Certification Authority - G3" -# Serial: 206684696279472310254277870180966723415 -# MD5 Fingerprint: cd:68:b6:a7:c7:c4:ce:75:e0:1d:4f:57:44:61:92:09 -# SHA1 Fingerprint: 13:2d:0d:45:53:4b:69:97:cd:b2:d5:c3:39:e2:55:76:60:9b:5c:c6 -# SHA256 Fingerprint: eb:04:cf:5e:b1:f3:9a:fa:76:2f:2b:b1:20:f2:96:cb:a5:20:c1:b9:7d:b1:58:95:65:b8:1c:b9:a1:7b:72:44 ------BEGIN CERTIFICATE----- -MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQsw -CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl -cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu -LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT -aWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp -dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD -VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT -aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ -bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu -IENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg -LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMu6nFL8eB8aHm8b -N3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1EUGO+i2t -KmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGu -kxUccLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBm -CC+Vk7+qRy+oRpfwEuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJ -Xwzw3sJ2zq/3avL6QaaiMxTJ5Xpj055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWu -imi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAERSWwauSCPc/L8my/uRan2Te -2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5fj267Cz3qWhMe -DGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC -/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565p -F4ErWjfJXir0xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGt -TxzhT5yvDwyd93gN2PQ1VoDat20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ== ------END CERTIFICATE----- - -# Issuer: CN=VeriSign Class 4 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only -# Subject: CN=VeriSign Class 4 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only -# Label: "Verisign Class 4 Public Primary Certification Authority - G3" -# Serial: 314531972711909413743075096039378935511 -# MD5 Fingerprint: db:c8:f2:27:2e:b1:ea:6a:29:23:5d:fe:56:3e:33:df -# SHA1 Fingerprint: c8:ec:8c:87:92:69:cb:4b:ab:39:e9:8d:7e:57:67:f3:14:95:73:9d -# SHA256 Fingerprint: e3:89:36:0d:0f:db:ae:b3:d2:50:58:4b:47:30:31:4e:22:2f:39:c1:56:a0:20:14:4e:8d:96:05:61:79:15:06 ------BEGIN CERTIFICATE----- -MIIEGjCCAwICEQDsoKeLbnVqAc/EfMwvlF7XMA0GCSqGSIb3DQEBBQUAMIHKMQsw -CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl -cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu -LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT -aWduIENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp -dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD -VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT -aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ -bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu -IENsYXNzIDQgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg -LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK3LpRFpxlmr8Y+1 -GQ9Wzsy1HyDkniYlS+BzZYlZ3tCD5PUPtbut8XzoIfzk6AzufEUiGXaStBO3IFsJ -+mGuqPKljYXCKtbeZjbSmwL0qJJgfJxptI8kHtCGUvYynEFYHiK9zUVilQhu0Gbd -U6LM8BDcVHOLBKFGMzNcF0C5nk3T875Vg+ixiY5afJqWIpA7iCXy0lOIAgwLePLm -NxdLMEYH5IBtptiWLugs+BGzOA1mppvqySNb247i8xOOGlktqgLw7KSHZtzBP/XY -ufTsgsbSPZUd5cBPhMnZo0QoBmrXRazwa2rvTl/4EYIeOGM0ZlDUPpNz+jDDZq3/ -ky2X7wMCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAj/ola09b5KROJ1WrIhVZPMq1 -CtRK26vdoV9TxaBXOcLORyu+OshWv8LZJxA6sQU8wHcxuzrTBXttmhwwjIDLk5Mq -g6sFUYICABFna/OIYUdfA5PVWw3g8dShMjWFsjrbsIKr0csKvE+MW8VLADsfKoKm -fjaF3H48ZwC15DtS4KjrXRX5xm3wrR0OhbepmnMUWluPQSjA1egtTaRezarZ7c7c -2NU8Qh0XwRJdRTjDOPP8hS6DRkiy1yBfkjaP53kPmF6Z6PDQpLv1U70qzlmwr25/ -bLvSHgCwIe34QWKCudiyxLtGUPMxxY8BqHTr9Xgn2uf3ZkPznoM+IKrDNWCRzg== ------END CERTIFICATE----- - -# Issuer: CN=Entrust.net Secure Server Certification Authority O=Entrust.net OU=www.entrust.net/CPS incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited -# Subject: CN=Entrust.net Secure Server Certification Authority O=Entrust.net OU=www.entrust.net/CPS incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited -# Label: "Entrust.net Secure Server CA" -# Serial: 927650371 -# MD5 Fingerprint: df:f2:80:73:cc:f1:e6:61:73:fc:f5:42:e9:c5:7c:ee -# SHA1 Fingerprint: 99:a6:9b:e6:1a:fe:88:6b:4d:2b:82:00:7c:b8:54:fc:31:7e:15:39 -# SHA256 Fingerprint: 62:f2:40:27:8c:56:4c:4d:d8:bf:7d:9d:4f:6f:36:6e:a8:94:d2:2f:5f:34:d9:89:a9:83:ac:ec:2f:ff:ed:50 ------BEGIN CERTIFICATE----- -MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC -VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u -ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc -KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u -ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw05OTA1 -MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIGA1UE -ChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5j -b3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF -bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUg -U2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUA -A4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQaO2f55M28Qpku0f1BBc/ -I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5gXpa0zf3 -wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OC -AdcwggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHb -oIHYpIHVMIHSMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5 -BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1p -dHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1pdGVk -MTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRp -b24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu -dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0 -MFqBDzIwMTkwNTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8Bdi -E1U9s/8KAGv7UISX8+1i0BowHQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAa -MAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EABAwwChsEVjQuMAMCBJAwDQYJKoZI -hvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyNEwr75Ji174z4xRAN -95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9n9cd -2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI= ------END CERTIFICATE----- - -# Issuer: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited -# Subject: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited -# Label: "Entrust.net Premium 2048 Secure Server CA" -# Serial: 946059622 -# MD5 Fingerprint: ba:21:ea:20:d6:dd:db:8f:c1:57:8b:40:ad:a1:fc:fc -# SHA1 Fingerprint: 80:1d:62:d0:7b:44:9d:5c:5c:03:5c:98:ea:61:fa:44:3c:2a:58:fe -# SHA256 Fingerprint: d1:c3:39:ea:27:84:eb:87:0f:93:4f:c5:63:4e:4a:a9:ad:55:05:01:64:01:f2:64:65:d3:7a:57:46:63:35:9f ------BEGIN CERTIFICATE----- -MIIEXDCCA0SgAwIBAgIEOGO5ZjANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML -RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp -bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5 -IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRp -ZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQxNzUwNTFaFw0xOTEy -MjQxODIwNTFaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3 -LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxp -YWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEG -A1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArU1LqRKGsuqjIAcVFmQq -K0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOLGp18EzoOH1u3Hs/lJBQe -sYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSrhRSGlVuX -MlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVT -XTzWnLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/ -HoZdenoVve8AjhUiVBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH -4QIDAQABo3QwcjARBglghkgBhvhCAQEEBAMCAAcwHwYDVR0jBBgwFoAUVeSB0RGA -vtiJuQijMfmhJAkWuXAwHQYDVR0OBBYEFFXkgdERgL7YibkIozH5oSQJFrlwMB0G -CSqGSIb2fQdBAAQQMA4bCFY1LjA6NC4wAwIEkDANBgkqhkiG9w0BAQUFAAOCAQEA -WUesIYSKF8mciVMeuoCFGsY8Tj6xnLZ8xpJdGGQC49MGCBFhfGPjK50xA3B20qMo -oPS7mmNz7W3lKtvtFKkrxjYR0CvrB4ul2p5cGZ1WEvVUKcgF7bISKo30Axv/55IQ -h7A6tcOdBTcSo8f0FbnVpDkWm1M6I5HxqIKiaohowXkCIryqptau37AUX7iH0N18 -f3v/rxzP5tsHrV7bhZ3QKw0z2wTR5klAEyt2+z7pnIkPFc4YsIV4IU9rTw76NmfN -B/L/CNDi3tm/Kq+4h4YhPATKt5Rof8886ZjXOP/swNlQ8C5LWK5Gb9Auw2DaclVy -vUxFnmG6v4SBkgPR0ml8xQ== ------END CERTIFICATE----- - -# Issuer: CN=Baltimore CyberTrust Root O=Baltimore OU=CyberTrust -# Subject: CN=Baltimore CyberTrust Root O=Baltimore OU=CyberTrust -# Label: "Baltimore CyberTrust Root" -# Serial: 33554617 -# MD5 Fingerprint: ac:b6:94:a5:9c:17:e0:d7:91:52:9b:b1:97:06:a6:e4 -# SHA1 Fingerprint: d4:de:20:d0:5e:66:fc:53:fe:1a:50:88:2c:78:db:28:52:ca:e4:74 -# SHA256 Fingerprint: 16:af:57:a9:f6:76:b0:ab:12:60:95:aa:5e:ba:de:f2:2a:b3:11:19:d6:44:ac:95:cd:4b:93:db:f3:f2:6a:eb ------BEGIN CERTIFICATE----- -MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ -RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD -VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX -DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y -ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy -VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr -mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr -IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK -mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu -XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy -dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye -jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1 -BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3 -DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92 -9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx -jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0 -Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz -ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS -R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp ------END CERTIFICATE----- - -# Issuer: CN=Equifax Secure Global eBusiness CA-1 O=Equifax Secure Inc. -# Subject: CN=Equifax Secure Global eBusiness CA-1 O=Equifax Secure Inc. -# Label: "Equifax Secure Global eBusiness CA" -# Serial: 1 -# MD5 Fingerprint: 8f:5d:77:06:27:c4:98:3c:5b:93:78:e7:d7:7d:9b:cc -# SHA1 Fingerprint: 7e:78:4a:10:1c:82:65:cc:2d:e1:f1:6d:47:b4:40:ca:d9:0a:19:45 -# SHA256 Fingerprint: 5f:0b:62:ea:b5:e3:53:ea:65:21:65:16:58:fb:b6:53:59:f4:43:28:0a:4a:fb:d1:04:d7:7d:10:f9:f0:4c:07 ------BEGIN CERTIFICATE----- -MIICkDCCAfmgAwIBAgIBATANBgkqhkiG9w0BAQQFADBaMQswCQYDVQQGEwJVUzEc -MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEtMCsGA1UEAxMkRXF1aWZheCBT -ZWN1cmUgR2xvYmFsIGVCdXNpbmVzcyBDQS0xMB4XDTk5MDYyMTA0MDAwMFoXDTIw -MDYyMTA0MDAwMFowWjELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0VxdWlmYXggU2Vj -dXJlIEluYy4xLTArBgNVBAMTJEVxdWlmYXggU2VjdXJlIEdsb2JhbCBlQnVzaW5l -c3MgQ0EtMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAuucXkAJlsTRVPEnC -UdXfp9E3j9HngXNBUmCbnaEXJnitx7HoJpQytd4zjTov2/KaelpzmKNc6fuKcxtc -58O/gGzNqfTWK8D3+ZmqY6KxRwIP1ORROhI8bIpaVIRw28HFkM9yRcuoWcDNM50/ -o5brhTMhHD4ePmBudpxnhcXIw2ECAwEAAaNmMGQwEQYJYIZIAYb4QgEBBAQDAgAH -MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUvqigdHJQa0S3ySPY+6j/s1dr -aGwwHQYDVR0OBBYEFL6ooHRyUGtEt8kj2Puo/7NXa2hsMA0GCSqGSIb3DQEBBAUA -A4GBADDiAVGqx+pf2rnQZQ8w1j7aDRRJbpGTJxQx78T3LUX47Me/okENI7SS+RkA -Z70Br83gcfxaz2TE4JaY0KNA4gGK7ycH8WUBikQtBmV1UsCGECAhX2xrD2yuCRyv -8qIYNMR1pHMc8Y3c7635s3a0kr/clRAevsvIO1qEYBlWlKlV ------END CERTIFICATE----- - -# Issuer: CN=Equifax Secure eBusiness CA-1 O=Equifax Secure Inc. -# Subject: CN=Equifax Secure eBusiness CA-1 O=Equifax Secure Inc. -# Label: "Equifax Secure eBusiness CA 1" -# Serial: 4 -# MD5 Fingerprint: 64:9c:ef:2e:44:fc:c6:8f:52:07:d0:51:73:8f:cb:3d -# SHA1 Fingerprint: da:40:18:8b:91:89:a3:ed:ee:ae:da:97:fe:2f:9d:f5:b7:d1:8a:41 -# SHA256 Fingerprint: cf:56:ff:46:a4:a1:86:10:9d:d9:65:84:b5:ee:b5:8a:51:0c:42:75:b0:e5:f9:4f:40:bb:ae:86:5e:19:f6:73 ------BEGIN CERTIFICATE----- -MIICgjCCAeugAwIBAgIBBDANBgkqhkiG9w0BAQQFADBTMQswCQYDVQQGEwJVUzEc -MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBT -ZWN1cmUgZUJ1c2luZXNzIENBLTEwHhcNOTkwNjIxMDQwMDAwWhcNMjAwNjIxMDQw -MDAwWjBTMQswCQYDVQQGEwJVUzEcMBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5j -LjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNzIENBLTEwgZ8wDQYJ -KoZIhvcNAQEBBQADgY0AMIGJAoGBAM4vGbwXt3fek6lfWg0XTzQaDJj0ItlZ1MRo -RvC0NcWFAyDGr0WlIVFFQesWWDYyb+JQYmT5/VGcqiTZ9J2DKocKIdMSODRsjQBu -WqDZQu4aIZX5UkxVWsUPOE9G+m34LjXWHXzr4vCwdYDIqROsvojvOm6rXyo4YgKw -Env+j6YDAgMBAAGjZjBkMBEGCWCGSAGG+EIBAQQEAwIABzAPBgNVHRMBAf8EBTAD -AQH/MB8GA1UdIwQYMBaAFEp4MlIR21kWNl7fwRQ2QGpHfEyhMB0GA1UdDgQWBBRK -eDJSEdtZFjZe38EUNkBqR3xMoTANBgkqhkiG9w0BAQQFAAOBgQB1W6ibAxHm6VZM -zfmpTMANmvPMZWnmJXbMWbfWVMMdzZmsGd20hdXgPfxiIKeES1hl8eL5lSE/9dR+ -WB5Hh1Q+WKG1tfgq73HnvMP2sUlG4tega+VWeponmHxGYhTnyfxuAxJ5gDgdSIKN -/Bf+KpYrtWKmpj29f5JZzVoqgrI3eQ== ------END CERTIFICATE----- - -# Issuer: O=Equifax Secure OU=Equifax Secure eBusiness CA-2 -# Subject: O=Equifax Secure OU=Equifax Secure eBusiness CA-2 -# Label: "Equifax Secure eBusiness CA 2" -# Serial: 930140085 -# MD5 Fingerprint: aa:bf:bf:64:97:da:98:1d:6f:c6:08:3a:95:70:33:ca -# SHA1 Fingerprint: 39:4f:f6:85:0b:06:be:52:e5:18:56:cc:10:e1:80:e8:82:b3:85:cc -# SHA256 Fingerprint: 2f:27:4e:48:ab:a4:ac:7b:76:59:33:10:17:75:50:6d:c3:0e:e3:8e:f6:ac:d5:c0:49:32:cf:e0:41:23:42:20 ------BEGIN CERTIFICATE----- -MIIDIDCCAomgAwIBAgIEN3DPtTANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV -UzEXMBUGA1UEChMORXF1aWZheCBTZWN1cmUxJjAkBgNVBAsTHUVxdWlmYXggU2Vj -dXJlIGVCdXNpbmVzcyBDQS0yMB4XDTk5MDYyMzEyMTQ0NVoXDTE5MDYyMzEyMTQ0 -NVowTjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkVxdWlmYXggU2VjdXJlMSYwJAYD -VQQLEx1FcXVpZmF4IFNlY3VyZSBlQnVzaW5lc3MgQ0EtMjCBnzANBgkqhkiG9w0B -AQEFAAOBjQAwgYkCgYEA5Dk5kx5SBhsoNviyoynF7Y6yEb3+6+e0dMKP/wXn2Z0G -vxLIPw7y1tEkshHe0XMJitSxLJgJDR5QRrKDpkWNYmi7hRsgcDKqQM2mll/EcTc/ -BPO3QSQ5BxoeLmFYoBIL5aXfxavqN3HMHMg3OrmXUqesxWoklE6ce8/AatbfIb0C -AwEAAaOCAQkwggEFMHAGA1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEX -MBUGA1UEChMORXF1aWZheCBTZWN1cmUxJjAkBgNVBAsTHUVxdWlmYXggU2VjdXJl -IGVCdXNpbmVzcyBDQS0yMQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTkw -NjIzMTIxNDQ1WjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUUJ4L6q9euSBIplBq -y/3YIHqngnYwHQYDVR0OBBYEFFCeC+qvXrkgSKZQasv92CB6p4J2MAwGA1UdEwQF -MAMBAf8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUA -A4GBAAyGgq3oThr1jokn4jVYPSm0B482UJW/bsGe68SQsoWou7dC4A8HOd/7npCy -0cE+U58DRLB+S/Rv5Hwf5+Kx5Lia78O9zt4LMjTZ3ijtM2vE1Nc9ElirfQkty3D1 -E4qUoSek1nDFbZS1yX2doNLGCEnZZpum0/QL3MUmV+GRMOrN ------END CERTIFICATE----- - -# Issuer: CN=AddTrust Class 1 CA Root O=AddTrust AB OU=AddTrust TTP Network -# Subject: CN=AddTrust Class 1 CA Root O=AddTrust AB OU=AddTrust TTP Network -# Label: "AddTrust Low-Value Services Root" -# Serial: 1 -# MD5 Fingerprint: 1e:42:95:02:33:92:6b:b9:5f:c0:7f:da:d6:b2:4b:fc -# SHA1 Fingerprint: cc:ab:0e:a0:4c:23:01:d6:69:7b:dd:37:9f:cd:12:eb:24:e3:94:9d -# SHA256 Fingerprint: 8c:72:09:27:9a:c0:4e:27:5e:16:d0:7f:d3:b7:75:e8:01:54:b5:96:80:46:e3:1f:52:dd:25:76:63:24:e9:a7 ------BEGIN CERTIFICATE----- -MIIEGDCCAwCgAwIBAgIBATANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQGEwJTRTEU -MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3 -b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwHhcNMDAwNTMw -MTAzODMxWhcNMjAwNTMwMTAzODMxWjBlMQswCQYDVQQGEwJTRTEUMBIGA1UEChML -QWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYD -VQQDExhBZGRUcnVzdCBDbGFzcyAxIENBIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUA -A4IBDwAwggEKAoIBAQCWltQhSWDia+hBBwzexODcEyPNwTXH+9ZOEQpnXvUGW2ul -CDtbKRY654eyNAbFvAWlA3yCyykQruGIgb3WntP+LVbBFc7jJp0VLhD7Bo8wBN6n -tGO0/7Gcrjyvd7ZWxbWroulpOj0OM3kyP3CCkplhbY0wCI9xP6ZIVxn4JdxLZlyl -dI+Yrsj5wAYi56xz36Uu+1LcsRVlIPo1Zmne3yzxbrww2ywkEtvrNTVokMsAsJch -PXQhI2U0K7t4WaPW4XY5mqRJjox0r26kmqPZm9I4XJuiGMx1I4S+6+JNM3GOGvDC -+Mcdoq0Dlyz4zyXG9rgkMbFjXZJ/Y/AlyVMuH79NAgMBAAGjgdIwgc8wHQYDVR0O -BBYEFJWxtPCUtr3H2tERCSG+wa9J/RB7MAsGA1UdDwQEAwIBBjAPBgNVHRMBAf8E -BTADAQH/MIGPBgNVHSMEgYcwgYSAFJWxtPCUtr3H2tERCSG+wa9J/RB7oWmkZzBl -MQswCQYDVQQGEwJTRTEUMBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFk -ZFRydXN0IFRUUCBOZXR3b3JrMSEwHwYDVQQDExhBZGRUcnVzdCBDbGFzcyAxIENB -IFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBACxtZBsfzQ3duQH6lmM0MkhHma6X -7f1yFqZzR1r0693p9db7RcwpiURdv0Y5PejuvE1Uhh4dbOMXJ0PhiVYrqW9yTkkz -43J8KiOavD7/KCrto/8cI7pDVwlnTUtiBi34/2ydYB7YHEt9tTEv2dB8Xfjea4MY -eDdXL+gzB2ffHsdrKpV2ro9Xo/D0UrSpUwjP4E/TelOL/bscVjby/rK25Xa71SJl -pz/+0WatC7xrmYbvP33zGDLKe8bjq2RGlfgmadlVg3sslgf/WSxEo8bl6ancoWOA -WiFeIc9TVPC6b4nbqKqVz4vjccweGyBECMB6tkD9xOQ14R0WHNC8K47Wcdk= ------END CERTIFICATE----- - -# Issuer: CN=AddTrust External CA Root O=AddTrust AB OU=AddTrust External TTP Network -# Subject: CN=AddTrust External CA Root O=AddTrust AB OU=AddTrust External TTP Network -# Label: "AddTrust External Root" -# Serial: 1 -# MD5 Fingerprint: 1d:35:54:04:85:78:b0:3f:42:42:4d:bf:20:73:0a:3f -# SHA1 Fingerprint: 02:fa:f3:e2:91:43:54:68:60:78:57:69:4d:f5:e4:5b:68:85:18:68 -# SHA256 Fingerprint: 68:7f:a4:51:38:22:78:ff:f0:c8:b1:1f:8d:43:d5:76:67:1c:6e:b2:bc:ea:b4:13:fb:83:d9:65:d0:6d:2f:f2 ------BEGIN CERTIFICATE----- -MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU -MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs -IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290 -MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux -FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h -bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v -dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt -H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9 -uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX -mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX -a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN -E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0 -WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD -VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0 -Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU -cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx -IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN -AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH -YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5 -6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC -Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX -c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a -mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ= ------END CERTIFICATE----- - -# Issuer: CN=AddTrust Public CA Root O=AddTrust AB OU=AddTrust TTP Network -# Subject: CN=AddTrust Public CA Root O=AddTrust AB OU=AddTrust TTP Network -# Label: "AddTrust Public Services Root" -# Serial: 1 -# MD5 Fingerprint: c1:62:3e:23:c5:82:73:9c:03:59:4b:2b:e9:77:49:7f -# SHA1 Fingerprint: 2a:b6:28:48:5e:78:fb:f3:ad:9e:79:10:dd:6b:df:99:72:2c:96:e5 -# SHA256 Fingerprint: 07:91:ca:07:49:b2:07:82:aa:d3:c7:d7:bd:0c:df:c9:48:58:35:84:3e:b2:d7:99:60:09:ce:43:ab:6c:69:27 ------BEGIN CERTIFICATE----- -MIIEFTCCAv2gAwIBAgIBATANBgkqhkiG9w0BAQUFADBkMQswCQYDVQQGEwJTRTEU -MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3 -b3JrMSAwHgYDVQQDExdBZGRUcnVzdCBQdWJsaWMgQ0EgUm9vdDAeFw0wMDA1MzAx -MDQxNTBaFw0yMDA1MzAxMDQxNTBaMGQxCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtB -ZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5ldHdvcmsxIDAeBgNV -BAMTF0FkZFRydXN0IFB1YmxpYyBDQSBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOC -AQ8AMIIBCgKCAQEA6Rowj4OIFMEg2Dybjxt+A3S72mnTRqX4jsIMEZBRpS9mVEBV -6tsfSlbunyNu9DnLoblv8n75XYcmYZ4c+OLspoH4IcUkzBEMP9smcnrHAZcHF/nX -GCwwfQ56HmIexkvA/X1id9NEHif2P0tEs7c42TkfYNVRknMDtABp4/MUTu7R3AnP -dzRGULD4EfL+OHn3Bzn+UZKXC1sIXzSGAa2Il+tmzV7R/9x98oTaunet3IAIx6eH -1lWfl2royBFkuucZKT8Rs3iQhCBSWxHveNCD9tVIkNAwHM+A+WD+eeSI8t0A65RF -62WUaUC6wNW0uLp9BBGo6zEFlpROWCGOn9Bg/QIDAQABo4HRMIHOMB0GA1UdDgQW -BBSBPjfYkrAfd59ctKtzquf2NGAv+jALBgNVHQ8EBAMCAQYwDwYDVR0TAQH/BAUw -AwEB/zCBjgYDVR0jBIGGMIGDgBSBPjfYkrAfd59ctKtzquf2NGAv+qFopGYwZDEL -MAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQLExRBZGRU -cnVzdCBUVFAgTmV0d29yazEgMB4GA1UEAxMXQWRkVHJ1c3QgUHVibGljIENBIFJv -b3SCAQEwDQYJKoZIhvcNAQEFBQADggEBAAP3FUr4JNojVhaTdt02KLmuG7jD8WS6 -IBh4lSknVwW8fCr0uVFV2ocC3g8WFzH4qnkuCRO7r7IgGRLlk/lL+YPoRNWyQSW/ -iHVv/xD8SlTQX/D67zZzfRs2RcYhbbQVuE7PnFylPVoAjgbjPGsye/Kf8Lb93/Ao -GEjwxrzQvzSAlsJKsW2Ox5BF3i9nrEUEo3rcVZLJR2bYGozH7ZxOmuASu7VqTITh -4SINhwBk/ox9Yjllpu9CtoAlEmEBqCQTcAARJl/6NVDFSMwGR+gn2HCNX2TmoUQm -XiLsks3/QppEIW1cxeMiHV9HEufOX1362KqxMy3ZdvJOOjMMK7MtkAY= ------END CERTIFICATE----- - -# Issuer: CN=AddTrust Qualified CA Root O=AddTrust AB OU=AddTrust TTP Network -# Subject: CN=AddTrust Qualified CA Root O=AddTrust AB OU=AddTrust TTP Network -# Label: "AddTrust Qualified Certificates Root" -# Serial: 1 -# MD5 Fingerprint: 27:ec:39:47:cd:da:5a:af:e2:9a:01:65:21:a9:4c:bb -# SHA1 Fingerprint: 4d:23:78:ec:91:95:39:b5:00:7f:75:8f:03:3b:21:1e:c5:4d:8b:cf -# SHA256 Fingerprint: 80:95:21:08:05:db:4b:bc:35:5e:44:28:d8:fd:6e:c2:cd:e3:ab:5f:b9:7a:99:42:98:8e:b8:f4:dc:d0:60:16 ------BEGIN CERTIFICATE----- -MIIEHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJTRTEU -MBIGA1UEChMLQWRkVHJ1c3QgQUIxHTAbBgNVBAsTFEFkZFRydXN0IFRUUCBOZXR3 -b3JrMSMwIQYDVQQDExpBZGRUcnVzdCBRdWFsaWZpZWQgQ0EgUm9vdDAeFw0wMDA1 -MzAxMDQ0NTBaFw0yMDA1MzAxMDQ0NTBaMGcxCzAJBgNVBAYTAlNFMRQwEgYDVQQK -EwtBZGRUcnVzdCBBQjEdMBsGA1UECxMUQWRkVHJ1c3QgVFRQIE5ldHdvcmsxIzAh -BgNVBAMTGkFkZFRydXN0IFF1YWxpZmllZCBDQSBSb290MIIBIjANBgkqhkiG9w0B -AQEFAAOCAQ8AMIIBCgKCAQEA5B6a/twJWoekn0e+EV+vhDTbYjx5eLfpMLXsDBwq -xBb/4Oxx64r1EW7tTw2R0hIYLUkVAcKkIhPHEWT/IhKauY5cLwjPcWqzZwFZ8V1G -87B4pfYOQnrjfxvM0PC3KP0q6p6zsLkEqv32x7SxuCqg+1jxGaBvcCV+PmlKfw8i -2O+tCBGaKZnhqkRFmhJePp1tUvznoD1oL/BLcHwTOK28FSXx1s6rosAx1i+f4P8U -WfyEk9mHfExUE+uf0S0R+Bg6Ot4l2ffTQO2kBhLEO+GRwVY18BTcZTYJbqukB8c1 -0cIDMzZbdSZtQvESa0NvS3GU+jQd7RNuyoB/mC9suWXY6QIDAQABo4HUMIHRMB0G -A1UdDgQWBBQ5lYtii1zJ1IC6WA+XPxUIQ8yYpzALBgNVHQ8EBAMCAQYwDwYDVR0T -AQH/BAUwAwEB/zCBkQYDVR0jBIGJMIGGgBQ5lYtii1zJ1IC6WA+XPxUIQ8yYp6Fr -pGkwZzELMAkGA1UEBhMCU0UxFDASBgNVBAoTC0FkZFRydXN0IEFCMR0wGwYDVQQL -ExRBZGRUcnVzdCBUVFAgTmV0d29yazEjMCEGA1UEAxMaQWRkVHJ1c3QgUXVhbGlm -aWVkIENBIFJvb3SCAQEwDQYJKoZIhvcNAQEFBQADggEBABmrder4i2VhlRO6aQTv -hsoToMeqT2QbPxj2qC0sVY8FtzDqQmodwCVRLae/DLPt7wh/bDxGGuoYQ992zPlm -hpwsaPXpF/gxsxjE1kh9I0xowX67ARRvxdlu3rsEQmr49lx95dr6h+sNNVJn0J6X -dgWTP5XHAeZpVTh/EGGZyeNfpso+gmNIquIISD6q8rKFYqa0p9m9N5xotS1WfbC3 -P6CxB9bpT9zeRXEwMn8bLgn5v1Kh7sKAPgZcLlVAwRv1cEWw3F369nJad9Jjzc9Y -iQBCYz95OdBEsIJuQRno3eDBiFrRHnGTHyQwdOUeqN48Jzd/g66ed8/wMLH/S5no -xqE= ------END CERTIFICATE----- - -# 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=GeoTrust Global CA O=GeoTrust Inc. -# Subject: CN=GeoTrust Global CA O=GeoTrust Inc. -# Label: "GeoTrust Global CA" -# Serial: 144470 -# MD5 Fingerprint: f7:75:ab:29:fb:51:4e:b7:77:5e:ff:05:3c:99:8e:f5 -# SHA1 Fingerprint: de:28:f4:a4:ff:e5:b9:2f:a3:c5:03:d1:a3:49:a7:f9:96:2a:82:12 -# SHA256 Fingerprint: ff:85:6a:2d:25:1d:cd:88:d3:66:56:f4:50:12:67:98:cf:ab:aa:de:40:79:9c:72:2d:e4:d2:b5:db:36:a7:3a ------BEGIN CERTIFICATE----- -MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT -MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i -YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG -EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg -R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9 -9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq -fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv -iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU -1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+ -bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW -MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA -ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l -uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn -Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS -tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF -PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un -hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV -5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw== ------END CERTIFICATE----- - -# Issuer: CN=GeoTrust Global CA 2 O=GeoTrust Inc. -# Subject: CN=GeoTrust Global CA 2 O=GeoTrust Inc. -# Label: "GeoTrust Global CA 2" -# Serial: 1 -# MD5 Fingerprint: 0e:40:a7:6c:de:03:5d:8f:d1:0f:e4:d1:8d:f9:6c:a9 -# SHA1 Fingerprint: a9:e9:78:08:14:37:58:88:f2:05:19:b0:6d:2b:0d:2b:60:16:90:7d -# SHA256 Fingerprint: ca:2d:82:a0:86:77:07:2f:8a:b6:76:4f:f0:35:67:6c:fe:3e:5e:32:5e:01:21:72:df:3f:92:09:6d:b7:9b:85 ------BEGIN CERTIFICATE----- -MIIDZjCCAk6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBEMQswCQYDVQQGEwJVUzEW -MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3QgR2xvYmFs -IENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMTkwMzA0MDUwMDAwWjBEMQswCQYDVQQG -EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEdMBsGA1UEAxMUR2VvVHJ1c3Qg -R2xvYmFsIENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDvPE1A -PRDfO1MA4Wf+lGAVPoWI8YkNkMgoI5kF6CsgncbzYEbYwbLVjDHZ3CB5JIG/NTL8 -Y2nbsSpr7iFY8gjpeMtvy/wWUsiRxP89c96xPqfCfWbB9X5SJBri1WeR0IIQ13hL -TytCOb1kLUCgsBDTOEhGiKEMuzozKmKY+wCdE1l/bztyqu6mD4b5BWHqZ38MN5aL -5mkWRxHCJ1kDs6ZgwiFAVvqgx306E+PsV8ez1q6diYD3Aecs9pYrEw15LNnA5IZ7 -S4wMcoKK+xfNAGw6EzywhIdLFnopsk/bHdQL82Y3vdj2V7teJHq4PIu5+pIaGoSe -2HSPqht/XvT+RSIhAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE -FHE4NvICMVNHK266ZUapEBVYIAUJMB8GA1UdIwQYMBaAFHE4NvICMVNHK266ZUap -EBVYIAUJMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQUFAAOCAQEAA/e1K6td -EPx7srJerJsOflN4WT5CBP51o62sgU7XAotexC3IUnbHLB/8gTKY0UvGkpMzNTEv -/NgdRN3ggX+d6YvhZJFiCzkIjKx0nVnZellSlxG5FntvRdOW2TF9AjYPnDtuzywN -A0ZF66D0f0hExghAzN4bcLUprbqLOzRldRtxIR0sFAqwlpW41uryZfspuk/qkZN0 -abby/+Ea0AzRdoXLiiW9l14sbxWZJue2Kf8i7MkCx1YAzUm5s2x7UwQa4qjJqhIF -I8LO57sEAszAR6LkxCkvW0VXiVHuPOtSCP8HNR6fNWpHSlaY0VqFH4z1Ir+rzoPz -4iIprn2DQKi6bA== ------END CERTIFICATE----- - -# Issuer: CN=GeoTrust Universal CA O=GeoTrust Inc. -# Subject: CN=GeoTrust Universal CA O=GeoTrust Inc. -# Label: "GeoTrust Universal CA" -# Serial: 1 -# MD5 Fingerprint: 92:65:58:8b:a2:1a:31:72:73:68:5c:b4:a5:7a:07:48 -# SHA1 Fingerprint: e6:21:f3:35:43:79:05:9a:4b:68:30:9d:8a:2f:74:22:15:87:ec:79 -# SHA256 Fingerprint: a0:45:9b:9f:63:b2:25:59:f5:fa:5d:4c:6d:b3:f9:f7:2f:f1:93:42:03:35:78:f0:73:bf:1d:1b:46:cb:b9:12 ------BEGIN CERTIFICATE----- -MIIFaDCCA1CgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJVUzEW -MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgVW5pdmVy -c2FsIENBMB4XDTA0MDMwNDA1MDAwMFoXDTI5MDMwNDA1MDAwMFowRTELMAkGA1UE -BhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xHjAcBgNVBAMTFUdlb1RydXN0 -IFVuaXZlcnNhbCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKYV -VaCjxuAfjJ0hUNfBvitbtaSeodlyWL0AG0y/YckUHUWCq8YdgNY96xCcOq9tJPi8 -cQGeBvV8Xx7BDlXKg5pZMK4ZyzBIle0iN430SppyZj6tlcDgFgDgEB8rMQ7XlFTT -QjOgNB0eRXbdT8oYN+yFFXoZCPzVx5zw8qkuEKmS5j1YPakWaDwvdSEYfyh3peFh -F7em6fgemdtzbvQKoiFs7tqqhZJmr/Z6a4LauiIINQ/PQvE1+mrufislzDoR5G2v -c7J2Ha3QsnhnGqQ5HFELZ1aD/ThdDc7d8Lsrlh/eezJS/R27tQahsiFepdaVaH/w -mZ7cRQg+59IJDTWU3YBOU5fXtQlEIGQWFwMCTFMNaN7VqnJNk22CDtucvc+081xd -VHppCZbW2xHBjXWotM85yM48vCR85mLK4b19p71XZQvk/iXttmkQ3CgaRr0BHdCX -teGYO8A3ZNY9lO4L4fUorgtWv3GLIylBjobFS1J72HGrH4oVpjuDWtdYAVHGTEHZ -f9hBZ3KiKN9gg6meyHv8U3NyWfWTehd2Ds735VzZC1U0oqpbtWpU5xPKV+yXbfRe -Bi9Fi1jUIxaS5BZuKGNZMN9QAZxjiRqf2xeUgnA3wySemkfWWspOqGmJch+RbNt+ -nhutxx9z3SxPGWX9f5NAEC7S8O08ni4oPmkmM8V7AgMBAAGjYzBhMA8GA1UdEwEB -/wQFMAMBAf8wHQYDVR0OBBYEFNq7LqqwDLiIJlF0XG0D08DYj3rWMB8GA1UdIwQY -MBaAFNq7LqqwDLiIJlF0XG0D08DYj3rWMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG -9w0BAQUFAAOCAgEAMXjmx7XfuJRAyXHEqDXsRh3ChfMoWIawC/yOsjmPRFWrZIRc -aanQmjg8+uUfNeVE44B5lGiku8SfPeE0zTBGi1QrlaXv9z+ZhP015s8xxtxqv6fX -IwjhmF7DWgh2qaavdy+3YL1ERmrvl/9zlcGO6JP7/TG37FcREUWbMPEaiDnBTzyn -ANXH/KttgCJwpQzgXQQpAvvLoJHRfNbDflDVnVi+QTjruXU8FdmbyUqDWcDaU/0z -uzYYm4UPFd3uLax2k7nZAY1IEKj79TiG8dsKxr2EoyNB3tZ3b4XUhRxQ4K5RirqN -Pnbiucon8l+f725ZDQbYKxek0nxru18UGkiPGkzns0ccjkxFKyDuSN/n3QmOGKja -QI2SJhFTYXNd673nxE0pN2HrrDktZy4W1vUAg4WhzH92xH3kt0tm7wNFYGm2DFKW -koRepqO1pD4r2czYG0eq8kTaT/kD6PAUyz/zg97QwVTjt+gKN02LIFkDMBmhLMi9 -ER/frslKxfMnZmaGrGiR/9nmUxwPi1xpZQomyB40w11Re9epnAahNt3ViZS82eQt -DF4JbAiXfKM9fJP/P6EUp8+1Xevb2xzEdt+Iub1FBZUbrvxGakyvSOPOrg/Sfuvm -bJxPgWp6ZKy7PtXny3YuxadIwVyQD8vIP/rmMuGNG2+k5o7Y+SlIis5z/iw= ------END CERTIFICATE----- - -# Issuer: CN=GeoTrust Universal CA 2 O=GeoTrust Inc. -# Subject: CN=GeoTrust Universal CA 2 O=GeoTrust Inc. -# Label: "GeoTrust Universal CA 2" -# Serial: 1 -# MD5 Fingerprint: 34:fc:b8:d0:36:db:9e:14:b3:c2:f2:db:8f:e4:94:c7 -# SHA1 Fingerprint: 37:9a:19:7b:41:85:45:35:0c:a6:03:69:f3:3c:2e:af:47:4f:20:79 -# SHA256 Fingerprint: a0:23:4f:3b:c8:52:7c:a5:62:8e:ec:81:ad:5d:69:89:5d:a5:68:0d:c9:1d:1c:b8:47:7f:33:f8:78:b9:5b:0b ------BEGIN CERTIFICATE----- -MIIFbDCCA1SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBHMQswCQYDVQQGEwJVUzEW -MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVy -c2FsIENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMjkwMzA0MDUwMDAwWjBHMQswCQYD -VQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1 -c3QgVW5pdmVyc2FsIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC -AQCzVFLByT7y2dyxUxpZKeexw0Uo5dfR7cXFS6GqdHtXr0om/Nj1XqduGdt0DE81 -WzILAePb63p3NeqqWuDW6KFXlPCQo3RWlEQwAx5cTiuFJnSCegx2oG9NzkEtoBUG -FF+3Qs17j1hhNNwqCPkuwwGmIkQcTAeC5lvO0Ep8BNMZcyfwqph/Lq9O64ceJHdq -XbboW0W63MOhBW9Wjo8QJqVJwy7XQYci4E+GymC16qFjwAGXEHm9ADwSbSsVsaxL -se4YuU6W3Nx2/zu+z18DwPw76L5GG//aQMJS9/7jOvdqdzXQ2o3rXhhqMcceujwb -KNZrVMaqW9eiLBsZzKIC9ptZvTdrhrVtgrrY6slWvKk2WP0+GfPtDCapkzj4T8Fd -IgbQl+rhrcZV4IErKIM6+vR7IVEAvlI4zs1meaj0gVbi0IMJR1FbUGrP20gaXT73 -y/Zl92zxlfgCOzJWgjl6W70viRu/obTo/3+NjN8D8WBOWBFM66M/ECuDmgFz2ZRt -hAAnZqzwcEAJQpKtT5MNYQlRJNiS1QuUYbKHsu3/mjX/hVTK7URDrBs8FmtISgoc -QIgfksILAAX/8sgCSqSqqcyZlpwvWOB94b67B9xfBHJcMTTD7F8t4D1kkCLm0ey4 -Lt1ZrtmhN79UNdxzMk+MBB4zsslG8dhcyFVQyWi9qLo2CQIDAQABo2MwYTAPBgNV -HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAfBgNV -HSMEGDAWgBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAOBgNVHQ8BAf8EBAMCAYYwDQYJ -KoZIhvcNAQEFBQADggIBAGbBxiPz2eAubl/oz66wsCVNK/g7WJtAJDday6sWSf+z -dXkzoS9tcBc0kf5nfo/sm+VegqlVHy/c1FEHEv6sFj4sNcZj/NwQ6w2jqtB8zNHQ -L1EuxBRa3ugZ4T7GzKQp5y6EqgYweHZUcyiYWTjgAA1i00J9IZ+uPTqM1fp3DRgr -Fg5fNuH8KrUwJM/gYwx7WBr+mbpCErGR9Hxo4sjoryzqyX6uuyo9DRXcNJW2GHSo -ag/HtPQTxORb7QrSpJdMKu0vbBKJPfEncKpqA1Ihn0CoZ1Dy81of398j9tx4TuaY -T1U6U+Pv8vSfx3zYWK8pIpe44L2RLrB27FcRz+8pRPPphXpgY+RdM4kX2TGq2tbz -GDVyz4crL2MjhF2EjD9XoIj8mZEoJmmZ1I+XRL6O1UixpCgp8RW04eWe3fiPpm8m -1wk8OhwRDqZsN/etRIcsKMfYdIKz0G9KV7s1KSegi+ghp4dkNl3M2Basx7InQJJV -OCiNUW7dFGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH -6aLcr34YEoP9VhdBLtUpgn2Z9DH2canPLAEnpQW5qrJITirvn5NSUZU8UnOOVkwX -QMAJKOSLakhT2+zNVVXxxvjpoixMptEmX36vWkzaH6byHCx+rgIW0lbQL1dTR+iS ------END CERTIFICATE----- - -# Issuer: CN=America Online Root Certification Authority 1 O=America Online Inc. -# Subject: CN=America Online Root Certification Authority 1 O=America Online Inc. -# Label: "America Online Root Certification Authority 1" -# Serial: 1 -# MD5 Fingerprint: 14:f1:08:ad:9d:fa:64:e2:89:e7:1c:cf:a8:ad:7d:5e -# SHA1 Fingerprint: 39:21:c1:15:c1:5d:0e:ca:5c:cb:5b:c4:f0:7d:21:d8:05:0b:56:6a -# SHA256 Fingerprint: 77:40:73:12:c6:3a:15:3d:5b:c0:0b:4e:51:75:9c:df:da:c2:37:dc:2a:33:b6:79:46:e9:8e:9b:fa:68:0a:e3 ------BEGIN CERTIFICATE----- -MIIDpDCCAoygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEc -MBoGA1UEChMTQW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBP -bmxpbmUgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAxMB4XDTAyMDUyODA2 -MDAwMFoXDTM3MTExOTIwNDMwMFowYzELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0Ft -ZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2EgT25saW5lIFJvb3Qg -Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMTCCASIwDQYJKoZIhvcNAQEBBQADggEP -ADCCAQoCggEBAKgv6KRpBgNHw+kqmP8ZonCaxlCyfqXfaE0bfA+2l2h9LaaLl+lk -hsmj76CGv2BlnEtUiMJIxUo5vxTjWVXlGbR0yLQFOVwWpeKVBeASrlmLojNoWBym -1BW32J/X3HGrfpq/m44zDyL9Hy7nBzbvYjnF3cu6JRQj3gzGPTzOggjmZj7aUTsW -OqMFf6Dch9Wc/HKpoH145LcxVR5lu9RhsCFg7RAycsWSJR74kEoYeEfffjA3PlAb -2xzTa5qGUwew76wGePiEmf4hjUyAtgyC9mZweRrTT6PP8c9GsEsPPt2IYriMqQko -O3rHl+Ee5fSfwMCuJKDIodkP1nsmgmkyPacCAwEAAaNjMGEwDwYDVR0TAQH/BAUw -AwEB/zAdBgNVHQ4EFgQUAK3Zo/Z59m50qX8zPYEX10zPM94wHwYDVR0jBBgwFoAU -AK3Zo/Z59m50qX8zPYEX10zPM94wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB -BQUAA4IBAQB8itEfGDeC4Liwo+1WlchiYZwFos3CYiZhzRAW18y0ZTTQEYqtqKkF -Zu90821fnZmv9ov761KyBZiibyrFVL0lvV+uyIbqRizBs73B6UlwGBaXCBOMIOAb -LjpHyx7kADCVW/RFo8AasAFOq73AI25jP4BKxQft3OJvx8Fi8eNy1gTIdGcL+oir -oQHIb/AUr9KZzVGTfu0uOMe9zkZQPXLjeSWdm4grECDdpbgyn43gKd8hdIaC2y+C -MMbHNYaz+ZZfRtsMRf3zUMNvxsNIrUam4SdHCh0Om7bCd39j8uB9Gr784N/Xx6ds -sPmuujz9dLQR6FgNgLzTqIA6me11zEZ7 ------END CERTIFICATE----- - -# Issuer: CN=America Online Root Certification Authority 2 O=America Online Inc. -# Subject: CN=America Online Root Certification Authority 2 O=America Online Inc. -# Label: "America Online Root Certification Authority 2" -# Serial: 1 -# MD5 Fingerprint: d6:ed:3c:ca:e2:66:0f:af:10:43:0d:77:9b:04:09:bf -# SHA1 Fingerprint: 85:b5:ff:67:9b:0c:79:96:1f:c8:6e:44:22:00:46:13:db:17:92:84 -# SHA256 Fingerprint: 7d:3b:46:5a:60:14:e5:26:c0:af:fc:ee:21:27:d2:31:17:27:ad:81:1c:26:84:2d:00:6a:f3:73:06:cc:80:bd ------BEGIN CERTIFICATE----- -MIIFpDCCA4ygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEc -MBoGA1UEChMTQW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBP -bmxpbmUgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAyMB4XDTAyMDUyODA2 -MDAwMFoXDTM3MDkyOTE0MDgwMFowYzELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0Ft -ZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2EgT25saW5lIFJvb3Qg -Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIP -ADCCAgoCggIBAMxBRR3pPU0Q9oyxQcngXssNt79Hc9PwVU3dxgz6sWYFas14tNwC -206B89enfHG8dWOgXeMHDEjsJcQDIPT/DjsS/5uN4cbVG7RtIuOx238hZK+GvFci -KtZHgVdEglZTvYYUAQv8f3SkWq7xuhG1m1hagLQ3eAkzfDJHA1zEpYNI9FdWboE2 -JxhP7JsowtS013wMPgwr38oE18aO6lhOqKSlGBxsRZijQdEt0sdtjRnxrXm3gT+9 -BoInLRBYBbV4Bbkv2wxrkJB+FFk4u5QkE+XRnRTf04JNRvCAOVIyD+OEsnpD8l7e -Xz8d3eOyG6ChKiMDbi4BFYdcpnV1x5dhvt6G3NRI270qv0pV2uh9UPu0gBe4lL8B -PeraunzgWGcXuVjgiIZGZ2ydEEdYMtA1fHkqkKJaEBEjNa0vzORKW6fIJ/KD3l67 -Xnfn6KVuY8INXWHQjNJsWiEOyiijzirplcdIz5ZvHZIlyMbGwcEMBawmxNJ10uEq -Z8A9W6Wa6897GqidFEXlD6CaZd4vKL3Ob5Rmg0gp2OpljK+T2WSfVVcmv2/LNzGZ -o2C7HK2JNDJiuEMhBnIMoVxtRsX6Kc8w3onccVvdtjc+31D1uAclJuW8tf48ArO3 -+L5DwYcRlJ4jbBeKuIonDFRH8KmzwICMoCfrHRnjB453cMor9H124HhnAgMBAAGj -YzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFE1FwWg4u3OpaaEg5+31IqEj -FNeeMB8GA1UdIwQYMBaAFE1FwWg4u3OpaaEg5+31IqEjFNeeMA4GA1UdDwEB/wQE -AwIBhjANBgkqhkiG9w0BAQUFAAOCAgEAZ2sGuV9FOypLM7PmG2tZTiLMubekJcmn -xPBUlgtk87FYT15R/LKXeydlwuXK5w0MJXti4/qftIe3RUavg6WXSIylvfEWK5t2 -LHo1YGwRgJfMqZJS5ivmae2p+DYtLHe/YUjRYwu5W1LtGLBDQiKmsXeu3mnFzccc -obGlHBD7GL4acN3Bkku+KVqdPzW+5X1R+FXgJXUjhx5c3LqdsKyzadsXg8n33gy8 -CNyRnqjQ1xU3c6U1uPx+xURABsPr+CKAXEfOAuMRn0T//ZoyzH1kUQ7rVyZ2OuMe -IjzCpjbdGe+n/BLzJsBZMYVMnNjP36TMzCmT/5RtdlwTCJfy7aULTd3oyWgOZtMA -DjMSW7yV5TKQqLPGbIOtd+6Lfn6xqavT4fG2wLHqiMDn05DpKJKUe2h7lyoKZy2F -AjgQ5ANh1NolNscIWC2hp1GvMApJ9aZphwctREZ2jirlmjvXGKL8nDgQzMY70rUX -Om/9riW99XJZZLF0KjhfGEzfz3EEWjbUvy+ZnOjZurGV5gJLIaFb1cFPj65pbVPb -AZO1XB4Y3WRayhgoPmMEEf0cjQAPuDffZ4qdZqkCapH/E8ovXYO8h5Ns3CRRFgQl -Zvqz2cK6Kb6aSDiCmfS/O0oxGfm/jiEzFMpPVF/7zvuPcX/9XhmgD0uRuMRUvAaw -RY8mkaKO/qk= ------END CERTIFICATE----- - -# Issuer: CN=AAA Certificate Services O=Comodo CA Limited -# Subject: CN=AAA Certificate Services O=Comodo CA Limited -# Label: "Comodo AAA Services root" -# Serial: 1 -# MD5 Fingerprint: 49:79:04:b0:eb:87:19:ac:47:b0:bc:11:51:9b:74:d0 -# SHA1 Fingerprint: d1:eb:23:a4:6d:17:d6:8f:d9:25:64:c2:f1:f1:60:17:64:d8:e3:49 -# SHA256 Fingerprint: d7:a7:a0:fb:5d:7e:27:31:d7:71:e9:48:4e:bc:de:f7:1d:5f:0c:3e:0a:29:48:78:2b:c8:3e:e0:ea:69:9e:f4 ------BEGIN CERTIFICATE----- -MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb -MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow -GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj -YXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL -MAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE -BwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM -GEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP -ADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua -BtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe -3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4 -YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR -rOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm -ez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU -oBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF -MAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v -QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t -b2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF -AAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q -GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz -Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2 -G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi -l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3 -smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== ------END CERTIFICATE----- - -# Issuer: CN=Secure Certificate Services O=Comodo CA Limited -# Subject: CN=Secure Certificate Services O=Comodo CA Limited -# Label: "Comodo Secure Services root" -# Serial: 1 -# MD5 Fingerprint: d3:d9:bd:ae:9f:ac:67:24:b3:c8:1b:52:e1:b9:a9:bd -# SHA1 Fingerprint: 4a:65:d5:f4:1d:ef:39:b8:b8:90:4a:4a:d3:64:81:33:cf:c7:a1:d1 -# SHA256 Fingerprint: bd:81:ce:3b:4f:65:91:d1:1a:67:b5:fc:7a:47:fd:ef:25:52:1b:f9:aa:4e:18:b9:e3:df:2e:34:a7:80:3b:e8 ------BEGIN CERTIFICATE----- -MIIEPzCCAyegAwIBAgIBATANBgkqhkiG9w0BAQUFADB+MQswCQYDVQQGEwJHQjEb -MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow -GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEkMCIGA1UEAwwbU2VjdXJlIENlcnRp -ZmljYXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVow -fjELMAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G -A1UEBwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxJDAiBgNV -BAMMG1NlY3VyZSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEB -BQADggEPADCCAQoCggEBAMBxM4KK0HDrc4eCQNUd5MvJDkKQ+d40uaG6EfQlhfPM -cm3ye5drswfxdySRXyWP9nQ95IDC+DwN879A6vfIUtFyb+/Iq0G4bi4XKpVpDM3S -HpR7LZQdqnXXs5jLrLxkU0C8j6ysNstcrbvd4JQX7NFc0L/vpZXJkMWwrPsbQ996 -CF23uPJAGysnnlDOXmWCiIxe004MeuoIkbY2qitC++rCoznl2yY4rYsK7hljxxwk -3wN42ubqwUcaCwtGCd0C/N7Lh1/XMGNooa7cMqG6vv5Eq2i2pRcV/b3Vp6ea5EQz -6YiO/O1R65NxTq0B50SOqy3LqP4BSUjwwN3HaNiS/j0CAwEAAaOBxzCBxDAdBgNV -HQ4EFgQUPNiTiMLAggnMAZkGkyDpnnAJY08wDgYDVR0PAQH/BAQDAgEGMA8GA1Ud -EwEB/wQFMAMBAf8wgYEGA1UdHwR6MHgwO6A5oDeGNWh0dHA6Ly9jcmwuY29tb2Rv -Y2EuY29tL1NlY3VyZUNlcnRpZmljYXRlU2VydmljZXMuY3JsMDmgN6A1hjNodHRw -Oi8vY3JsLmNvbW9kby5uZXQvU2VjdXJlQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmww -DQYJKoZIhvcNAQEFBQADggEBAIcBbSMdflsXfcFhMs+P5/OKlFlm4J4oqF7Tt/Q0 -5qo5spcWxYJvMqTpjOev/e/C6LlLqqP05tqNZSH7uoDrJiiFGv45jN5bBAS0VPmj -Z55B+glSzAVIqMk/IQQezkhr/IXownuvf7fM+F86/TXGDe+X3EyrEeFryzHRbPtI -gKvcnDe4IRRLDXE97IMzbtFuMhbsmMcWi1mmNKsFVy2T96oTy9IT4rcuO81rUBcJ -aD61JlfutuC23bkpgHl9j6PwpCikFcSF9CfUa7/lXORlAnZUtOM3ZiTTGWHIUhDl -izeauan5Hb/qmZJhlv8BzaFfDbxxvA6sCx1HRR3B7Hzs/Sk= ------END CERTIFICATE----- - -# Issuer: CN=Trusted Certificate Services O=Comodo CA Limited -# Subject: CN=Trusted Certificate Services O=Comodo CA Limited -# Label: "Comodo Trusted Services root" -# Serial: 1 -# MD5 Fingerprint: 91:1b:3f:6e:cd:9e:ab:ee:07:fe:1f:71:d2:b3:61:27 -# SHA1 Fingerprint: e1:9f:e3:0e:8b:84:60:9e:80:9b:17:0d:72:a8:c5:ba:6e:14:09:bd -# SHA256 Fingerprint: 3f:06:e5:56:81:d4:96:f5:be:16:9e:b5:38:9f:9f:2b:8f:f6:1e:17:08:df:68:81:72:48:49:cd:5d:27:cb:69 ------BEGIN CERTIFICATE----- -MIIEQzCCAyugAwIBAgIBATANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJHQjEb -MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow -GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDElMCMGA1UEAwwcVHJ1c3RlZCBDZXJ0 -aWZpY2F0ZSBTZXJ2aWNlczAeFw0wNDAxMDEwMDAwMDBaFw0yODEyMzEyMzU5NTla -MH8xCzAJBgNVBAYTAkdCMRswGQYDVQQIDBJHcmVhdGVyIE1hbmNoZXN0ZXIxEDAO -BgNVBAcMB1NhbGZvcmQxGjAYBgNVBAoMEUNvbW9kbyBDQSBMaW1pdGVkMSUwIwYD -VQQDDBxUcnVzdGVkIENlcnRpZmljYXRlIFNlcnZpY2VzMIIBIjANBgkqhkiG9w0B -AQEFAAOCAQ8AMIIBCgKCAQEA33FvNlhTWvI2VFeAxHQIIO0Yfyod5jWaHiWsnOWW -fnJSoBVC21ndZHoa0Lh73TkVvFVIxO06AOoxEbrycXQaZ7jPM8yoMa+j49d/vzMt -TGo87IvDktJTdyR0nAducPy9C1t2ul/y/9c3S0pgePfw+spwtOpZqqPOSC+pw7IL -fhdyFgymBwwbOM/JYrc/oJOlh0Hyt3BAd9i+FHzjqMB6juljatEPmsbS9Is6FARW -1O24zG71++IsWL1/T2sr92AkWCTOJu80kTrV44HQsvAEAtdbtz6SrGsSivnkBbA7 -kUlcsutT6vifR4buv5XAwAaf0lteERv0xwQ1KdJVXOTt6wIDAQABo4HJMIHGMB0G -A1UdDgQWBBTFe1i97doladL3WRaoszLAeydb9DAOBgNVHQ8BAf8EBAMCAQYwDwYD -VR0TAQH/BAUwAwEB/zCBgwYDVR0fBHwwejA8oDqgOIY2aHR0cDovL2NybC5jb21v -ZG9jYS5jb20vVHJ1c3RlZENlcnRpZmljYXRlU2VydmljZXMuY3JsMDqgOKA2hjRo -dHRwOi8vY3JsLmNvbW9kby5uZXQvVHJ1c3RlZENlcnRpZmljYXRlU2VydmljZXMu -Y3JsMA0GCSqGSIb3DQEBBQUAA4IBAQDIk4E7ibSvuIQSTI3S8NtwuleGFTQQuS9/ -HrCoiWChisJ3DFBKmwCL2Iv0QeLQg4pKHBQGsKNoBXAxMKdTmw7pSqBYaWcOrp32 -pSxBvzwGa+RZzG0Q8ZZvH9/0BAKkn0U+yNj6NkZEUD+Cl5EfKNsYEYwq5GWDVxIS -jBc/lDb+XbDABHcTuPQV1T84zJQ6VdCsmPW6AF/ghhmBeC8owH7TzEIK9a5QoNE+ -xqFx7D+gIIxmOom0jtTYsU0lR+4viMi14QVFwL4Ucd56/Y57fU0IlqUSc/Atyjcn -dBInTMu2l+nZrghtWjlA3QVHdWpaIbOjGM9O9y5Xt5hwXsjEeLBi ------END CERTIFICATE----- - -# Issuer: CN=UTN - DATACorp SGC O=The USERTRUST Network OU=http://www.usertrust.com -# Subject: CN=UTN - DATACorp SGC O=The USERTRUST Network OU=http://www.usertrust.com -# Label: "UTN DATACorp SGC Root CA" -# Serial: 91374294542884689855167577680241077609 -# MD5 Fingerprint: b3:a5:3e:77:21:6d:ac:4a:c0:c9:fb:d5:41:3d:ca:06 -# SHA1 Fingerprint: 58:11:9f:0e:12:82:87:ea:50:fd:d9:87:45:6f:4f:78:dc:fa:d6:d4 -# SHA256 Fingerprint: 85:fb:2f:91:dd:12:27:5a:01:45:b6:36:53:4f:84:02:4a:d6:8b:69:b8:ee:88:68:4f:f7:11:37:58:05:b3:48 ------BEGIN CERTIFICATE----- -MIIEXjCCA0agAwIBAgIQRL4Mi1AAIbQR0ypoBqmtaTANBgkqhkiG9w0BAQUFADCB -kzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug -Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho -dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xGzAZBgNVBAMTElVUTiAtIERBVEFDb3Jw -IFNHQzAeFw05OTA2MjQxODU3MjFaFw0xOTA2MjQxOTA2MzBaMIGTMQswCQYDVQQG -EwJVUzELMAkGA1UECBMCVVQxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MR4wHAYD -VQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxITAfBgNVBAsTGGh0dHA6Ly93d3cu -dXNlcnRydXN0LmNvbTEbMBkGA1UEAxMSVVROIC0gREFUQUNvcnAgU0dDMIIBIjAN -BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3+5YEKIrblXEjr8uRgnn4AgPLit6 -E5Qbvfa2gI5lBZMAHryv4g+OGQ0SR+ysraP6LnD43m77VkIVni5c7yPeIbkFdicZ -D0/Ww5y0vpQZY/KmEQrrU0icvvIpOxboGqBMpsn0GFlowHDyUwDAXlCCpVZvNvlK -4ESGoE1O1kduSUrLZ9emxAW5jh70/P/N5zbgnAVssjMiFdC04MwXwLLA9P4yPykq -lXvY8qdOD1R8oQ2AswkDwf9c3V6aPryuvEeKaq5xyh+xKrhfQgUL7EYw0XILyulW -bfXv33i+Ybqypa4ETLyorGkVl73v67SMvzX41MPRKA5cOp9wGDMgd8SirwIDAQAB -o4GrMIGoMAsGA1UdDwQEAwIBxjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRT -MtGzz3/64PGgXYVOktKeRR20TzA9BgNVHR8ENjA0MDKgMKAuhixodHRwOi8vY3Js -LnVzZXJ0cnVzdC5jb20vVVROLURBVEFDb3JwU0dDLmNybDAqBgNVHSUEIzAhBggr -BgEFBQcDAQYKKwYBBAGCNwoDAwYJYIZIAYb4QgQBMA0GCSqGSIb3DQEBBQUAA4IB -AQAnNZcAiosovcYzMB4p/OL31ZjUQLtgyr+rFywJNn9Q+kHcrpY6CiM+iVnJowft -Gzet/Hy+UUla3joKVAgWRcKZsYfNjGjgaQPpxE6YsjuMFrMOoAyYUJuTqXAJyCyj -j98C5OBxOvG0I3KgqgHf35g+FFCgMSa9KOlaMCZ1+XtgHI3zzVAmbQQnmt/VDUVH -KWss5nbZqSl9Mt3JNjy9rjXxEZ4du5A/EkdOjtd+D2JzHVImOBwYSf0wdJrE5SIv -2MCN7ZF6TACPcn9d2t0bi0Vr591pl6jFVkwPDPafepE39peC4N1xaf92P2BNPM/3 -mfnGV/TJVTl4uix5yaaIK/QI ------END CERTIFICATE----- - -# Issuer: CN=UTN-USERFirst-Hardware O=The USERTRUST Network OU=http://www.usertrust.com -# Subject: CN=UTN-USERFirst-Hardware O=The USERTRUST Network OU=http://www.usertrust.com -# Label: "UTN USERFirst Hardware Root CA" -# Serial: 91374294542884704022267039221184531197 -# MD5 Fingerprint: 4c:56:41:e5:0d:bb:2b:e8:ca:a3:ed:18:08:ad:43:39 -# SHA1 Fingerprint: 04:83:ed:33:99:ac:36:08:05:87:22:ed:bc:5e:46:00:e3:be:f9:d7 -# SHA256 Fingerprint: 6e:a5:47:41:d0:04:66:7e:ed:1b:48:16:63:4a:a3:a7:9e:6e:4b:96:95:0f:82:79:da:fc:8d:9b:d8:81:21:37 ------BEGIN CERTIFICATE----- -MIIEdDCCA1ygAwIBAgIQRL4Mi1AAJLQR0zYq/mUK/TANBgkqhkiG9w0BAQUFADCB -lzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug -Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho -dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3Qt -SGFyZHdhcmUwHhcNOTkwNzA5MTgxMDQyWhcNMTkwNzA5MTgxOTIyWjCBlzELMAkG -A1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEe -MBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8v -d3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdh -cmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCx98M4P7Sof885glFn -0G2f0v9Y8+efK+wNiVSZuTiZFvfgIXlIwrthdBKWHTxqctU8EGc6Oe0rE81m65UJ -M6Rsl7HoxuzBdXmcRl6Nq9Bq/bkqVRcQVLMZ8Jr28bFdtqdt++BxF2uiiPsA3/4a -MXcMmgF6sTLjKwEHOG7DpV4jvEWbe1DByTCP2+UretNb+zNAHqDVmBe8i4fDidNd -oI6yqqr2jmmIBsX6iSHzCJ1pLgkzmykNRg+MzEk0sGlRvfkGzWitZky8PqxhvQqI -DsjfPe58BEydCl5rkdbux+0ojatNh4lz0G6k0B4WixThdkQDf2Os5M1JnMWS9Ksy -oUhbAgMBAAGjgbkwgbYwCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8wHQYD -VR0OBBYEFKFyXyYbKJhDlV0HN9WFlp1L0sNFMEQGA1UdHwQ9MDswOaA3oDWGM2h0 -dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNFUkZpcnN0LUhhcmR3YXJlLmNy -bDAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwUGCCsGAQUFBwMGBggrBgEF -BQcDBzANBgkqhkiG9w0BAQUFAAOCAQEARxkP3nTGmZev/K0oXnWO6y1n7k57K9cM -//bey1WiCuFMVGWTYGufEpytXoMs61quwOQt9ABjHbjAbPLPSbtNk28Gpgoiskli -CE7/yMgUsogWXecB5BKV5UU0s4tpvc+0hY91UZ59Ojg6FEgSxvunOxqNDYJAB+gE -CJChicsZUN/KHAG8HQQZexB2lzvukJDKxA4fFm517zP4029bHpbj4HR3dHuKom4t -3XbWOTCC8KucUvIqx69JXn7HaOWCgchqJ/kniCrVWFCVH/A7HFe7fRQ5YiuayZSS -KqMiDP+JJn1fIytH1xUdqWqeUQ0qUZ6B+dQ7XnASfxAynB67nfhmqA== ------END CERTIFICATE----- - -# Issuer: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com -# Subject: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com -# Label: "XRamp Global CA Root" -# Serial: 107108908803651509692980124233745014957 -# MD5 Fingerprint: a1:0b:44:b3:ca:10:d8:00:6e:9d:0f:d8:0f:92:0a:d1 -# SHA1 Fingerprint: b8:01:86:d1:eb:9c:86:a5:41:04:cf:30:54:f3:4c:52:b7:e5:58:c6 -# SHA256 Fingerprint: ce:cd:dc:90:50:99:d8:da:df:c5:b1:d2:09:b7:37:cb:e2:c1:8c:fb:2c:10:c0:ff:0b:cf:0d:32:86:fc:1a:a2 ------BEGIN CERTIFICATE----- -MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCB -gjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEk -MCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRY -UmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQxMTAxMTcx -NDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3 -dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2Vy -dmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB -dXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS6 -38eMpSe2OAtp87ZOqCwuIR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCP -KZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMxfoArtYzAQDsRhtDLooY2YKTVMIJt2W7Q -DxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FEzG+gSqmUsE3a56k0enI4 -qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqsAxcZZPRa -JSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNVi -PvryxS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0P -BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASs -jVy16bYbMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0 -eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQEwDQYJKoZIhvcNAQEFBQAD -ggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc/Kh4ZzXxHfAR -vbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt -qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLa -IR9NmXmd4c8nnxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSy -i6mx5O+aGtA9aZnuqCij4Tyz8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQ -O+7ETPTsJ3xCwnR8gooJybQDJbw= ------END CERTIFICATE----- - -# Issuer: O=The Go Daddy Group, Inc. OU=Go Daddy Class 2 Certification Authority -# Subject: O=The Go Daddy Group, Inc. OU=Go Daddy Class 2 Certification Authority -# Label: "Go Daddy Class 2 CA" -# Serial: 0 -# MD5 Fingerprint: 91:de:06:25:ab:da:fd:32:17:0c:bb:25:17:2a:84:67 -# SHA1 Fingerprint: 27:96:ba:e6:3f:18:01:e2:77:26:1b:a0:d7:77:70:02:8f:20:ee:e4 -# SHA256 Fingerprint: c3:84:6b:f2:4b:9e:93:ca:64:27:4c:0e:c6:7c:1e:cc:5e:02:4f:fc:ac:d2:d7:40:19:35:0e:81:fe:54:6a:e4 ------BEGIN CERTIFICATE----- -MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh -MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE -YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3 -MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo -ZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg -MiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN -ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA -PVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w -wdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi -EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY -avx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+ -YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE -sNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h -/t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5 -IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj -YXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD -ggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy -OO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P -TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ -HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER -dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf -ReYNnyicsbkqWletNw+vHX/bvZ8= ------END CERTIFICATE----- - -# Issuer: O=Starfield Technologies, Inc. OU=Starfield Class 2 Certification Authority -# Subject: O=Starfield Technologies, Inc. OU=Starfield Class 2 Certification Authority -# Label: "Starfield Class 2 CA" -# Serial: 0 -# MD5 Fingerprint: 32:4a:4b:bb:c8:63:69:9b:be:74:9a:c6:dd:1d:46:24 -# SHA1 Fingerprint: ad:7e:1c:28:b0:64:ef:8f:60:03:40:20:14:c3:d0:e3:37:0e:b5:8a -# SHA256 Fingerprint: 14:65:fa:20:53:97:b8:76:fa:a6:f0:a9:95:8e:55:90:e4:0f:cc:7f:aa:4f:b7:c2:c8:67:75:21:fb:5f:b6:58 ------BEGIN CERTIFICATE----- -MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl -MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp -U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw -NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE -ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp -ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3 -DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf -8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN -+lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0 -X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa -K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA -1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G -A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR -zt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0 -YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD -bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w -DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3 -L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D -eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl -xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp -VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY -WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q= ------END CERTIFICATE----- - -# Issuer: CN=StartCom Certification Authority O=StartCom Ltd. OU=Secure Digital Certificate Signing -# Subject: CN=StartCom Certification Authority O=StartCom Ltd. OU=Secure Digital Certificate Signing -# Label: "StartCom Certification Authority" -# Serial: 1 -# MD5 Fingerprint: 22:4d:8f:8a:fc:f7:35:c2:bb:57:34:90:7b:8b:22:16 -# SHA1 Fingerprint: 3e:2b:f7:f2:03:1b:96:f3:8c:e6:c4:d8:a8:5d:3e:2d:58:47:6a:0f -# SHA256 Fingerprint: c7:66:a9:be:f2:d4:07:1c:86:3a:31:aa:49:20:e8:13:b2:d1:98:60:8c:b7:b7:cf:e2:11:43:b8:36:df:09:ea ------BEGIN CERTIFICATE----- -MIIHyTCCBbGgAwIBAgIBATANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEW -MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg -Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh -dGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0NjM2WhcNMzYwOTE3MTk0NjM2WjB9 -MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMi -U2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3Rh -cnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA -A4ICDwAwggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZk -pMyONvg45iPwbm2xPN1yo4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rf -OQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/C -Ji/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/deMotHweXMAEtcnn6RtYT -Kqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt2PZE4XNi -HzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMM -Av+Z6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w -+2OqqGwaVLRcJXrJosmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+ -Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3 -Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVcUjyJthkqcwEKDwOzEmDyei+B -26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT37uMdBNSSwID -AQABo4ICUjCCAk4wDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAa4wHQYDVR0OBBYE -FE4L7xqkQFulF2mHMMo0aEPQQa7yMGQGA1UdHwRdMFswLKAqoCiGJmh0dHA6Ly9j -ZXJ0LnN0YXJ0Y29tLm9yZy9zZnNjYS1jcmwuY3JsMCugKaAnhiVodHRwOi8vY3Js -LnN0YXJ0Y29tLm9yZy9zZnNjYS1jcmwuY3JsMIIBXQYDVR0gBIIBVDCCAVAwggFM -BgsrBgEEAYG1NwEBATCCATswLwYIKwYBBQUHAgEWI2h0dHA6Ly9jZXJ0LnN0YXJ0 -Y29tLm9yZy9wb2xpY3kucGRmMDUGCCsGAQUFBwIBFilodHRwOi8vY2VydC5zdGFy -dGNvbS5vcmcvaW50ZXJtZWRpYXRlLnBkZjCB0AYIKwYBBQUHAgIwgcMwJxYgU3Rh -cnQgQ29tbWVyY2lhbCAoU3RhcnRDb20pIEx0ZC4wAwIBARqBl0xpbWl0ZWQgTGlh -YmlsaXR5LCByZWFkIHRoZSBzZWN0aW9uICpMZWdhbCBMaW1pdGF0aW9ucyogb2Yg -dGhlIFN0YXJ0Q29tIENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFBvbGljeSBhdmFp -bGFibGUgYXQgaHR0cDovL2NlcnQuc3RhcnRjb20ub3JnL3BvbGljeS5wZGYwEQYJ -YIZIAYb4QgEBBAQDAgAHMDgGCWCGSAGG+EIBDQQrFilTdGFydENvbSBGcmVlIFNT -TCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTANBgkqhkiG9w0BAQUFAAOCAgEAFmyZ -9GYMNPXQhV59CuzaEE44HF7fpiUFS5Eyweg78T3dRAlbB0mKKctmArexmvclmAk8 -jhvh3TaHK0u7aNM5Zj2gJsfyOZEdUauCe37Vzlrk4gNXcGmXCPleWKYK34wGmkUW -FjgKXlf2Ysd6AgXmvB618p70qSmD+LIU424oh0TDkBreOKk8rENNZEXO3SipXPJz -ewT4F+irsfMuXGRuczE6Eri8sxHkfY+BUZo7jYn0TZNmezwD7dOaHZrzZVD1oNB1 -ny+v8OqCQ5j4aZyJecRDjkZy42Q2Eq/3JR44iZB3fsNrarnDy0RLrHiQi+fHLB5L -EUTINFInzQpdn4XBidUaePKVEFMy3YCEZnXZtWgo+2EuvoSoOMCZEoalHmdkrQYu -L6lwhceWD3yJZfWOQ1QOq92lgDmUYMA0yZZwLKMS9R9Ie70cfmu3nZD0Ijuu+Pwq -yvqCUqDvr0tVk+vBtfAii6w0TiYiBKGHLHVKt+V9E9e4DGTANtLJL4YSjCMJwRuC -O3NJo2pXh5Tl1njFmUNj403gdy3hZZlyaQQaRwnmDwFWJPsfvw55qVguucQJAX6V -um0ABj6y6koQOdjQK/W/7HW/lwLFCRsI3FU34oH7N4RDYiDK51ZLZer+bMEkkySh -NOsF/5oirpt9P/FlUQqmMGqz9IgcgA38corog14= ------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=GeoTrust Primary Certification Authority O=GeoTrust Inc. -# Subject: CN=GeoTrust Primary Certification Authority O=GeoTrust Inc. -# Label: "GeoTrust Primary Certification Authority" -# Serial: 32798226551256963324313806436981982369 -# MD5 Fingerprint: 02:26:c3:01:5e:08:30:37:43:a9:d0:7d:cf:37:e6:bf -# SHA1 Fingerprint: 32:3c:11:8e:1b:f7:b8:b6:52:54:e2:e2:10:0d:d6:02:90:37:f0:96 -# SHA256 Fingerprint: 37:d5:10:06:c5:12:ea:ab:62:64:21:f1:ec:8c:92:01:3f:c5:f8:2a:e9:8e:e5:33:eb:46:19:b8:de:b4:d0:6c ------BEGIN CERTIFICATE----- -MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBY -MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMo -R2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEx -MjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgxCzAJBgNVBAYTAlVTMRYwFAYDVQQK -Ew1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQcmltYXJ5IENlcnRp -ZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC -AQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9 -AWbK7hWNb6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjA -ZIVcFU2Ix7e64HXprQU9nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE0 -7e9GceBrAqg1cmuXm2bgyxx5X9gaBGgeRwLmnWDiNpcB3841kt++Z8dtd1k7j53W -kBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGttm/81w7a4DSwDRp35+MI -mO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G -A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJ -KoZIhvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ1 -6CePbJC/kRYkRj5KTs4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl -4b7UVXGYNTq+k+qurUKykG/g/CFNNWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6K -oKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHaFloxt/m0cYASSJlyc1pZU8Fj -UjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG1riR/aYNKxoU -AT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk= ------END CERTIFICATE----- - -# Issuer: CN=thawte Primary Root CA O=thawte, Inc. OU=Certification Services Division/(c) 2006 thawte, Inc. - For authorized use only -# Subject: CN=thawte Primary Root CA O=thawte, Inc. OU=Certification Services Division/(c) 2006 thawte, Inc. - For authorized use only -# Label: "thawte Primary Root CA" -# Serial: 69529181992039203566298953787712940909 -# MD5 Fingerprint: 8c:ca:dc:0b:22:ce:f5:be:72:ac:41:1a:11:a8:d8:12 -# SHA1 Fingerprint: 91:c6:d6:ee:3e:8a:c8:63:84:e5:48:c2:99:29:5c:75:6c:81:7b:81 -# SHA256 Fingerprint: 8d:72:2f:81:a9:c1:13:c0:79:1d:f1:36:a2:96:6d:b2:6c:95:0a:97:1d:b4:6b:41:99:f4:ea:54:b7:8b:fb:9f ------BEGIN CERTIFICATE----- -MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCB -qTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf -Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw -MDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNV -BAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3MDAwMDAwWhcNMzYw -NzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5j -LjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYG -A1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl -IG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqG -SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCsoPD7gFnUnMekz52hWXMJEEUMDSxuaPFs -W0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ1CRfBsDMRJSUjQJib+ta -3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGcq/gcfomk -6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6 -Sk/KaAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94J -NqR32HuHUETVPm4pafs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBA -MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XP -r87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUFAAOCAQEAeRHAS7ORtvzw6WfU -DW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeEuzLlQRHAd9mz -YJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX -xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2 -/qxAeeWsEG89jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/ -LHbTY5xZ3Y+m4Q6gLkH3LpVHz7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7 -jVaMaA== ------END CERTIFICATE----- - -# Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G5 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2006 VeriSign, Inc. - For authorized use only -# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G5 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2006 VeriSign, Inc. - For authorized use only -# Label: "VeriSign Class 3 Public Primary Certification Authority - G5" -# Serial: 33037644167568058970164719475676101450 -# MD5 Fingerprint: cb:17:e4:31:67:3e:e2:09:fe:45:57:93:f3:0a:fa:1c -# SHA1 Fingerprint: 4e:b6:d5:78:49:9b:1c:cf:5f:58:1e:ad:56:be:3d:9b:67:44:a5:e5 -# SHA256 Fingerprint: 9a:cf:ab:7e:43:c8:d8:80:d0:6b:26:2a:94:de:ee:e4:b4:65:99:89:c3:d0:ca:f1:9b:af:64:05:e4:1a:b7:df ------BEGIN CERTIFICATE----- -MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCB -yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL -ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp -U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW -ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0 -aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCByjEL -MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW -ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2ln -biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp -U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y -aXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1 -nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbex -t0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIz -SdhDY2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQG -BO+QueQA5N06tRn/Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+ -rCpSx4/VBEnkjWNHiDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/ -NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E -BAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAH -BgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy -aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKv -MzEzMA0GCSqGSIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzE -p6B4Eq1iDkVwZMXnl2YtmAl+X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y -5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKEKQsTb47bDN0lAtukixlE0kF6BWlK -WE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiCKm0oHw0LxOXnGiYZ -4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vEZV8N -hnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq ------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=Network Solutions Certificate Authority O=Network Solutions L.L.C. -# Subject: CN=Network Solutions Certificate Authority O=Network Solutions L.L.C. -# Label: "Network Solutions Certificate Authority" -# Serial: 116697915152937497490437556386812487904 -# MD5 Fingerprint: d3:f3:a6:16:c0:fa:6b:1d:59:b1:2d:96:4d:0e:11:2e -# SHA1 Fingerprint: 74:f8:a3:c3:ef:e7:b3:90:06:4b:83:90:3c:21:64:60:20:e5:df:ce -# SHA256 Fingerprint: 15:f0:ba:00:a3:ac:7a:f3:ac:88:4c:07:2b:10:11:a0:77:bd:77:c0:97:f4:01:64:b2:f8:59:8a:bd:83:86:0c ------BEGIN CERTIFICATE----- -MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBi -MQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu -MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3Jp -dHkwHhcNMDYxMjAxMDAwMDAwWhcNMjkxMjMxMjM1OTU5WjBiMQswCQYDVQQGEwJV -UzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYDVQQDEydO -ZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0GCSqG -SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkvH6SMG3G2I4rC7xGzuAnlt7e+foS0zwz -c7MEL7xxjOWftiJgPl9dzgn/ggwbmlFQGiaJ3dVhXRncEg8tCqJDXRfQNJIg6nPP -OCwGJgl6cvf6UDL4wpPTaaIjzkGxzOTVHzbRijr4jGPiFFlp7Q3Tf2vouAPlT2rl -mGNpSAW+Lv8ztumXWWn4Zxmuk2GWRBXTcrA/vGp97Eh/jcOrqnErU2lBUzS1sLnF -BgrEsEX1QV1uiUV7PTsmjHTC5dLRfbIR1PtYMiKagMnc/Qzpf14Dl847ABSHJ3A4 -qY5usyd2mFHgBeMhqxrVhSI8KbWaFsWAqPS7azCPL0YCorEMIuDTAgMBAAGjgZcw -gZQwHQYDVR0OBBYEFCEwyfsA106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB/wQEAwIB -BjAPBgNVHRMBAf8EBTADAQH/MFIGA1UdHwRLMEkwR6BFoEOGQWh0dHA6Ly9jcmwu -bmV0c29sc3NsLmNvbS9OZXR3b3JrU29sdXRpb25zQ2VydGlmaWNhdGVBdXRob3Jp -dHkuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQC7rkvnt1frf6ott3NHhWrB5KUd5Oc8 -6fRZZXe1eltajSU24HqXLjjAV2CDmAaDn7l2em5Q4LqILPxFzBiwmZVRDuwduIj/ -h1AcgsLj4DKAv6ALR8jDMe+ZZzKATxcheQxpXN5eNK4CtSbqUN9/GGUsyfJj4akH -/nxxH2szJGoeBfcFaMBqEssuXmHLrijTfsK0ZpEmXzwuJF/LWA/rKOyvEZbz3Htv -wKeI8lN3s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHN -pGxlaKFJdlxDydi8NmdspZS11My5vWo1ViHe2MPr+8ukYEywVaCge1ey ------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=TC TrustCenter Class 2 CA II O=TC TrustCenter GmbH OU=TC TrustCenter Class 2 CA -# Subject: CN=TC TrustCenter Class 2 CA II O=TC TrustCenter GmbH OU=TC TrustCenter Class 2 CA -# Label: "TC TrustCenter Class 2 CA II" -# Serial: 941389028203453866782103406992443 -# MD5 Fingerprint: ce:78:33:5c:59:78:01:6e:18:ea:b9:36:a0:b9:2e:23 -# SHA1 Fingerprint: ae:50:83:ed:7c:f4:5c:bc:8f:61:c6:21:fe:68:5d:79:42:21:15:6e -# SHA256 Fingerprint: e6:b8:f8:76:64:85:f8:07:ae:7f:8d:ac:16:70:46:1f:07:c0:a1:3e:ef:3a:1f:f7:17:53:8d:7a:ba:d3:91:b4 ------BEGIN CERTIFICATE----- -MIIEqjCCA5KgAwIBAgIOLmoAAQACH9dSISwRXDswDQYJKoZIhvcNAQEFBQAwdjEL -MAkGA1UEBhMCREUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNV -BAsTGVRDIFRydXN0Q2VudGVyIENsYXNzIDIgQ0ExJTAjBgNVBAMTHFRDIFRydXN0 -Q2VudGVyIENsYXNzIDIgQ0EgSUkwHhcNMDYwMTEyMTQzODQzWhcNMjUxMjMxMjI1 -OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1c3RDZW50ZXIgR21i -SDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQTElMCMGA1UEAxMc -VEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQAD -ggEPADCCAQoCggEBAKuAh5uO8MN8h9foJIIRszzdQ2Lu+MNF2ujhoF/RKrLqk2jf -tMjWQ+nEdVl//OEd+DFwIxuInie5e/060smp6RQvkL4DUsFJzfb95AhmC1eKokKg -uNV/aVyQMrKXDcpK3EY+AlWJU+MaWss2xgdW94zPEfRMuzBwBJWl9jmM/XOBCH2J -XjIeIqkiRUuwZi4wzJ9l/fzLganx4Duvo4bRierERXlQXa7pIXSSTYtZgo+U4+lK -8edJsBTj9WLL1XK9H7nSn6DNqPoByNkN39r8R52zyFTfSUrxIan+GE7uSNQZu+99 -5OKdy1u2bv/jzVrndIIFuoAlOMvkaZ6vQaoahPUCAwEAAaOCATQwggEwMA8GA1Ud -EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTjq1RMgKHbVkO3 -kUrL84J6E1wIqzCB7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRy -dXN0Y2VudGVyLmRlL2NybC92Mi90Y19jbGFzc18yX2NhX0lJLmNybIaBn2xkYXA6 -Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBUcnVzdENlbnRlciUyMENsYXNz -JTIwMiUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21iSCxPVT1yb290 -Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u -TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEAjNfffu4bgBCzg/XbEeprS6iS -GNn3Bzn1LL4GdXpoUxUc6krtXvwjshOg0wn/9vYua0Fxec3ibf2uWWuFHbhOIprt -ZjluS5TmVfwLG4t3wVMTZonZKNaL80VKY7f9ewthXbhtvsPcW3nS7Yblok2+XnR8 -au0WOB9/WIFaGusyiC2y8zl3gK9etmF1KdsjTYjKUCjLhdLTEKJZbtOTVAB6okaV -hgWcqRmY5TFyDADiZ9lA4CQze28suVyrZZ0srHbqNZn1l7kPJOzHdiEoZa5X6AeI -dUpWoNIFOqTmjZKILPPy4cHGYdtBxceb9w4aUUXCYWvcZCcXjFq32nQozZfkvQ== ------END CERTIFICATE----- - -# Issuer: CN=TC TrustCenter Class 3 CA II O=TC TrustCenter GmbH OU=TC TrustCenter Class 3 CA -# Subject: CN=TC TrustCenter Class 3 CA II O=TC TrustCenter GmbH OU=TC TrustCenter Class 3 CA -# Label: "TC TrustCenter Class 3 CA II" -# Serial: 1506523511417715638772220530020799 -# MD5 Fingerprint: 56:5f:aa:80:61:12:17:f6:67:21:e6:2b:6d:61:56:8e -# SHA1 Fingerprint: 80:25:ef:f4:6e:70:c8:d4:72:24:65:84:fe:40:3b:8a:8d:6a:db:f5 -# SHA256 Fingerprint: 8d:a0:84:fc:f9:9c:e0:77:22:f8:9b:32:05:93:98:06:fa:5c:b8:11:e1:c8:13:f6:a1:08:c7:d3:36:b3:40:8e ------BEGIN CERTIFICATE----- -MIIEqjCCA5KgAwIBAgIOSkcAAQAC5aBd1j8AUb8wDQYJKoZIhvcNAQEFBQAwdjEL -MAkGA1UEBhMCREUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNV -BAsTGVRDIFRydXN0Q2VudGVyIENsYXNzIDMgQ0ExJTAjBgNVBAMTHFRDIFRydXN0 -Q2VudGVyIENsYXNzIDMgQ0EgSUkwHhcNMDYwMTEyMTQ0MTU3WhcNMjUxMjMxMjI1 -OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1c3RDZW50ZXIgR21i -SDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQTElMCMGA1UEAxMc -VEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQAD -ggEPADCCAQoCggEBALTgu1G7OVyLBMVMeRwjhjEQY0NVJz/GRcekPewJDRoeIMJW -Ht4bNwcwIi9v8Qbxq63WyKthoy9DxLCyLfzDlml7forkzMA5EpBCYMnMNWju2l+Q -Vl/NHE1bWEnrDgFPZPosPIlY2C8u4rBo6SI7dYnWRBpl8huXJh0obazovVkdKyT2 -1oQDZogkAHhg8fir/gKya/si+zXmFtGt9i4S5Po1auUZuV3bOx4a+9P/FRQI2Alq -ukWdFHlgfa9Aigdzs5OW03Q0jTo3Kd5c7PXuLjHCINy+8U9/I1LZW+Jk2ZyqBwi1 -Rb3R0DHBq1SfqdLDYmAD8bs5SpJKPQq5ncWg/jcCAwEAAaOCATQwggEwMA8GA1Ud -EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTUovyfs8PYA9NX -XAek0CSnwPIA1DCB7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRy -dXN0Y2VudGVyLmRlL2NybC92Mi90Y19jbGFzc18zX2NhX0lJLmNybIaBn2xkYXA6 -Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBUcnVzdENlbnRlciUyMENsYXNz -JTIwMyUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21iSCxPVT1yb290 -Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u -TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEANmDkcPcGIEPZIxpC8vijsrlN -irTzwppVMXzEO2eatN9NDoqTSheLG43KieHPOh6sHfGcMrSOWXaiQYUlN6AT0PV8 -TtXqluJucsG7Kv5sbviRmEb8yRtXW+rIGjs/sFGYPAfaLFkB2otE6OF0/ado3VS6 -g0bsyEa1+K+XwDsJHI/OcpY9M1ZwvJbL2NV9IJqDnxrcOfHFcqMRA/07QlIp2+gB -95tejNaNhk4Z+rwcvsUhpYeeeC422wlxo3I0+GzjBgnyXlal092Y+tTmBvTwtiBj -S+opvaqCZh77gaqnN60TGOaSw4HBM7uIHqHn4rS9MWwOUT1v+5ZWgOI2F9Hc5A== ------END CERTIFICATE----- - -# Issuer: CN=TC TrustCenter Universal CA I O=TC TrustCenter GmbH OU=TC TrustCenter Universal CA -# Subject: CN=TC TrustCenter Universal CA I O=TC TrustCenter GmbH OU=TC TrustCenter Universal CA -# Label: "TC TrustCenter Universal CA I" -# Serial: 601024842042189035295619584734726 -# MD5 Fingerprint: 45:e1:a5:72:c5:a9:36:64:40:9e:f5:e4:58:84:67:8c -# SHA1 Fingerprint: 6b:2f:34:ad:89:58:be:62:fd:b0:6b:5c:ce:bb:9d:d9:4f:4e:39:f3 -# SHA256 Fingerprint: eb:f3:c0:2a:87:89:b1:fb:7d:51:19:95:d6:63:b7:29:06:d9:13:ce:0d:5e:10:56:8a:8a:77:e2:58:61:67:e7 ------BEGIN CERTIFICATE----- -MIID3TCCAsWgAwIBAgIOHaIAAQAC7LdggHiNtgYwDQYJKoZIhvcNAQEFBQAweTEL -MAkGA1UEBhMCREUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxJDAiBgNV -BAsTG1RDIFRydXN0Q2VudGVyIFVuaXZlcnNhbCBDQTEmMCQGA1UEAxMdVEMgVHJ1 -c3RDZW50ZXIgVW5pdmVyc2FsIENBIEkwHhcNMDYwMzIyMTU1NDI4WhcNMjUxMjMx -MjI1OTU5WjB5MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1c3RDZW50ZXIg -R21iSDEkMCIGA1UECxMbVEMgVHJ1c3RDZW50ZXIgVW5pdmVyc2FsIENBMSYwJAYD -VQQDEx1UQyBUcnVzdENlbnRlciBVbml2ZXJzYWwgQ0EgSTCCASIwDQYJKoZIhvcN -AQEBBQADggEPADCCAQoCggEBAKR3I5ZEr5D0MacQ9CaHnPM42Q9e3s9B6DGtxnSR -JJZ4Hgmgm5qVSkr1YnwCqMqs+1oEdjneX/H5s7/zA1hV0qq34wQi0fiU2iIIAI3T -fCZdzHd55yx4Oagmcw6iXSVphU9VDprvxrlE4Vc93x9UIuVvZaozhDrzznq+VZeu -jRIPFDPiUHDDSYcTvFHe15gSWu86gzOSBnWLknwSaHtwag+1m7Z3W0hZneTvWq3z -wZ7U10VOylY0Ibw+F1tvdwxIAUMpsN0/lm7mlaoMwCC2/T42J5zjXM9OgdwZu5GQ -fezmlwQek8wiSdeXhrYTCjxDI3d+8NzmzSQfO4ObNDqDNOMCAwEAAaNjMGEwHwYD -VR0jBBgwFoAUkqR1LKSevoFE63n8isWVpesQdXMwDwYDVR0TAQH/BAUwAwEB/zAO -BgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFJKkdSyknr6BROt5/IrFlaXrEHVzMA0G -CSqGSIb3DQEBBQUAA4IBAQAo0uCG1eb4e/CX3CJrO5UUVg8RMKWaTzqwOuAGy2X1 -7caXJ/4l8lfmXpWMPmRgFVp/Lw0BxbFg/UU1z/CyvwbZ71q+s2IhtNerNXxTPqYn -8aEt2hojnczd7Dwtnic0XQ/CNnm8yUpiLe1r2X1BQ3y2qsrtYbE3ghUJGooWMNjs -ydZHcnhLEEYUjl8Or+zHL6sQ17bxbuyGssLoDZJz3KL0Dzq/YSMQiZxIQG5wALPT -ujdEWBF6AmqI8Dc08BnprNRlc/ZpjGSUOnmFKbAWKwyCPwacx/0QK54PLLae4xW/ -2TYcuiUaUj0a7CIMHOCkoj3w6DnPgcB77V0fb8XQC9eY ------END CERTIFICATE----- - -# Issuer: CN=Cybertrust Global Root O=Cybertrust, Inc -# Subject: CN=Cybertrust Global Root O=Cybertrust, Inc -# Label: "Cybertrust Global Root" -# Serial: 4835703278459682877484360 -# MD5 Fingerprint: 72:e4:4a:87:e3:69:40:80:77:ea:bc:e3:f4:ff:f0:e1 -# SHA1 Fingerprint: 5f:43:e5:b1:bf:f8:78:8c:ac:1c:c7:ca:4a:9a:c6:22:2b:cc:34:c6 -# SHA256 Fingerprint: 96:0a:df:00:63:e9:63:56:75:0c:29:65:dd:0a:08:67:da:0b:9c:bd:6e:77:71:4a:ea:fb:23:49:ab:39:3d:a3 ------BEGIN CERTIFICATE----- -MIIDoTCCAomgAwIBAgILBAAAAAABD4WqLUgwDQYJKoZIhvcNAQEFBQAwOzEYMBYG -A1UEChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2Jh -bCBSb290MB4XDTA2MTIxNTA4MDAwMFoXDTIxMTIxNTA4MDAwMFowOzEYMBYGA1UE -ChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2JhbCBS -b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+Mi8vRRQZhP/8NN5 -7CPytxrHjoXxEnOmGaoQ25yiZXRadz5RfVb23CO21O1fWLE3TdVJDm71aofW0ozS -J8bi/zafmGWgE07GKmSb1ZASzxQG9Dvj1Ci+6A74q05IlG2OlTEQXO2iLb3VOm2y -HLtgwEZLAfVJrn5GitB0jaEMAs7u/OePuGtm839EAL9mJRQr3RAwHQeWP032a7iP -t3sMpTjr3kfb1V05/Iin89cqdPHoWqI7n1C6poxFNcJQZZXcY4Lv3b93TZxiyWNz -FtApD0mpSPCzqrdsxacwOUBdrsTiXSZT8M4cIwhhqJQZugRiQOwfOHB3EgZxpzAY -XSUnpQIDAQABo4GlMIGiMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/ -MB0GA1UdDgQWBBS2CHsNesysIEyGVjJez6tuhS1wVzA/BgNVHR8EODA2MDSgMqAw -hi5odHRwOi8vd3d3Mi5wdWJsaWMtdHJ1c3QuY29tL2NybC9jdC9jdHJvb3QuY3Js -MB8GA1UdIwQYMBaAFLYIew16zKwgTIZWMl7Pq26FLXBXMA0GCSqGSIb3DQEBBQUA -A4IBAQBW7wojoFROlZfJ+InaRcHUowAl9B8Tq7ejhVhpwjCt2BWKLePJzYFa+HMj -Wqd8BfP9IjsO0QbE2zZMcwSO5bAi5MXzLqXZI+O4Tkogp24CJJ8iYGd7ix1yCcUx -XOl5n4BHPa2hCwcUPUf/A2kaDAtE52Mlp3+yybh2hO0j9n0Hq0V+09+zv+mKts2o -omcrUtW3ZfA5TGOgkXmTUg9U3YO7n9GPp1Nzw8v/MOx8BLjYRB+TX3EJIrduPuoc -A06dGiBh+4E37F78CkWr1+cXVdCg6mCbpvbjjFspwgZgFJ0tl0ypkxWdYcQBX0jW -WL1WMRJOEcgh4LMRkWXbtKaIOM5V ------END CERTIFICATE----- - -# Issuer: CN=GeoTrust Primary Certification Authority - G3 O=GeoTrust Inc. OU=(c) 2008 GeoTrust Inc. - For authorized use only -# Subject: CN=GeoTrust Primary Certification Authority - G3 O=GeoTrust Inc. OU=(c) 2008 GeoTrust Inc. - For authorized use only -# Label: "GeoTrust Primary Certification Authority - G3" -# Serial: 28809105769928564313984085209975885599 -# MD5 Fingerprint: b5:e8:34:36:c9:10:44:58:48:70:6d:2e:83:d4:b8:05 -# SHA1 Fingerprint: 03:9e:ed:b8:0b:e7:a0:3c:69:53:89:3b:20:d2:d9:32:3a:4c:2a:fd -# SHA256 Fingerprint: b4:78:b8:12:25:0d:f8:78:63:5c:2a:a7:ec:7d:15:5e:aa:62:5e:e8:29:16:e2:cd:29:43:61:88:6c:d1:fb:d4 ------BEGIN CERTIFICATE----- -MIID/jCCAuagAwIBAgIQFaxulBmyeUtB9iepwxgPHzANBgkqhkiG9w0BAQsFADCB -mDELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsT -MChjKSAyMDA4IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25s -eTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhv -cml0eSAtIEczMB4XDTA4MDQwMjAwMDAwMFoXDTM3MTIwMTIzNTk1OVowgZgxCzAJ -BgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykg -MjAwOCBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0 -BgNVBAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg -LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANziXmJYHTNXOTIz -+uvLh4yn1ErdBojqZI4xmKU4kB6Yzy5jK/BGvESyiaHAKAxJcCGVn2TAppMSAmUm -hsalifD614SgcK9PGpc/BkTVyetyEH3kMSj7HGHmKAdEc5IiaacDiGydY8hS2pgn -5whMcD60yRLBxWeDXTPzAxHsatBT4tG6NmCUgLthY2xbF37fQJQeqw3CIShwiP/W -JmxsYAQlTlV+fe+/lEjetx3dcI0FX4ilm/LC7urRQEFtYjgdVgbFA0dRIBn8exAL -DmKudlW/X3e+PkkBUz2YJQN2JFodtNuJ6nnltrM7P7pMKEF/BqxqjsHQ9gUdfeZC -huOl1UcCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw -HQYDVR0OBBYEFMR5yo6hTgMdHNxr2zFblD4/MH8tMA0GCSqGSIb3DQEBCwUAA4IB -AQAtxRPPVoB7eni9n64smefv2t+UXglpp+duaIy9cr5HqQ6XErhK8WTTOd8lNNTB -zU6B8A8ExCSzNJbGpqow32hhc9f5joWJ7w5elShKKiePEI4ufIbEAp7aDHdlDkQN -kv39sxY2+hENHYwOB4lqKVb3cvTdFZx3NWZXqxNT2I7BQMXXExZacse3aQHEerGD -AWh9jUGhlBjBJVz88P6DAod8DQ3PLghcSkANPuyBYeYk28rgDi0Hsj5W3I31QYUH -SJsMC8tJP33st/3LjWeJGqvtux6jAAgIFyqCXDFdRootD4abdNlF+9RAsXqqaC2G -spki4cErx5z481+oghLrGREt ------END CERTIFICATE----- - -# Issuer: CN=thawte Primary Root CA - G2 O=thawte, Inc. OU=(c) 2007 thawte, Inc. - For authorized use only -# Subject: CN=thawte Primary Root CA - G2 O=thawte, Inc. OU=(c) 2007 thawte, Inc. - For authorized use only -# Label: "thawte Primary Root CA - G2" -# Serial: 71758320672825410020661621085256472406 -# MD5 Fingerprint: 74:9d:ea:60:24:c4:fd:22:53:3e:cc:3a:72:d9:29:4f -# SHA1 Fingerprint: aa:db:bc:22:23:8f:c4:01:a1:27:bb:38:dd:f4:1d:db:08:9e:f0:12 -# SHA256 Fingerprint: a4:31:0d:50:af:18:a6:44:71:90:37:2a:86:af:af:8b:95:1f:fb:43:1d:83:7f:1e:56:88:b4:59:71:ed:15:57 ------BEGIN CERTIFICATE----- -MIICiDCCAg2gAwIBAgIQNfwmXNmET8k9Jj1Xm67XVjAKBggqhkjOPQQDAzCBhDEL -MAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjE4MDYGA1UECxMvKGMp -IDIwMDcgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAi -BgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMjAeFw0wNzExMDUwMDAw -MDBaFw0zODAxMTgyMzU5NTlaMIGEMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhh -d3RlLCBJbmMuMTgwNgYDVQQLEy8oYykgMjAwNyB0aGF3dGUsIEluYy4gLSBGb3Ig -YXV0aG9yaXplZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9v -dCBDQSAtIEcyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEotWcgnuVnfFSeIf+iha/ -BebfowJPDQfGAFG6DAJSLSKkQjnE/o/qycG+1E3/n3qe4rF8mq2nhglzh9HnmuN6 -papu+7qzcMBniKI11KOasf2twu8x+qi58/sIxpHR+ymVo0IwQDAPBgNVHRMBAf8E -BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUmtgAMADna3+FGO6Lts6K -DPgR4bswCgYIKoZIzj0EAwMDaQAwZgIxAN344FdHW6fmCsO99YCKlzUNG4k8VIZ3 -KMqh9HneteY4sPBlcIx/AlTCv//YoT7ZzwIxAMSNlPzcU9LcnXgWHxUzI1NS41ox -XZ3Krr0TKUQNJ1uo52icEvdYPy5yAlejj6EULg== ------END CERTIFICATE----- - -# Issuer: CN=thawte Primary Root CA - G3 O=thawte, Inc. OU=Certification Services Division/(c) 2008 thawte, Inc. - For authorized use only -# Subject: CN=thawte Primary Root CA - G3 O=thawte, Inc. OU=Certification Services Division/(c) 2008 thawte, Inc. - For authorized use only -# Label: "thawte Primary Root CA - G3" -# Serial: 127614157056681299805556476275995414779 -# MD5 Fingerprint: fb:1b:5d:43:8a:94:cd:44:c6:76:f2:43:4b:47:e7:31 -# SHA1 Fingerprint: f1:8b:53:8d:1b:e9:03:b6:a6:f0:56:43:5b:17:15:89:ca:f3:6b:f2 -# SHA256 Fingerprint: 4b:03:f4:58:07:ad:70:f2:1b:fc:2c:ae:71:c9:fd:e4:60:4c:06:4c:f5:ff:b6:86:ba:e5:db:aa:d7:fd:d3:4c ------BEGIN CERTIFICATE----- -MIIEKjCCAxKgAwIBAgIQYAGXt0an6rS0mtZLL/eQ+zANBgkqhkiG9w0BAQsFADCB -rjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf -Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw -MDggdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAiBgNV -BAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMzAeFw0wODA0MDIwMDAwMDBa -Fw0zNzEyMDEyMzU5NTlaMIGuMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhhd3Rl -LCBJbmMuMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9u -MTgwNgYDVQQLEy8oYykgMjAwOCB0aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXpl -ZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAtIEcz -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsr8nLPvb2FvdeHsbnndm -gcs+vHyu86YnmjSjaDFxODNi5PNxZnmxqWWjpYvVj2AtP0LMqmsywCPLLEHd5N/8 -YZzic7IilRFDGF/Eth9XbAoFWCLINkw6fKXRz4aviKdEAhN0cXMKQlkC+BsUa0Lf -b1+6a4KinVvnSr0eAXLbS3ToO39/fR8EtCab4LRarEc9VbjXsCZSKAExQGbY2SS9 -9irY7CFJXJv2eul/VTV+lmuNk5Mny5K76qxAwJ/C+IDPXfRa3M50hqY+bAtTyr2S -zhkGcuYMXDhpxwTWvGzOW/b3aJzcJRVIiKHpqfiYnODz1TEoYRFsZ5aNOZnLwkUk -OQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNV -HQ4EFgQUrWyqlGCc7eT/+j4KdCtjA/e2Wb8wDQYJKoZIhvcNAQELBQADggEBABpA -2JVlrAmSicY59BDlqQ5mU1143vokkbvnRFHfxhY0Cu9qRFHqKweKA3rD6z8KLFIW -oCtDuSWQP3CpMyVtRRooOyfPqsMpQhvfO0zAMzRbQYi/aytlryjvsvXDqmbOe1bu -t8jLZ8HJnBoYuMTDSQPxYA5QzUbF83d597YV4Djbxy8ooAw/dyZ02SUS2jHaGh7c -KUGRIjxpp7sC8rZcJwOJ9Abqm+RyguOhCcHpABnTPtRwa7pxpqpYrvS76Wy274fM -m7v/OeZWYdMKp8RcTGB7BXcmer/YB1IsYvdwY9k5vG8cwnncdimvzsUsZAReiDZu -MdRAGmI0Nj81Aa6sY6A= ------END CERTIFICATE----- - -# Issuer: CN=GeoTrust Primary Certification Authority - G2 O=GeoTrust Inc. OU=(c) 2007 GeoTrust Inc. - For authorized use only -# Subject: CN=GeoTrust Primary Certification Authority - G2 O=GeoTrust Inc. OU=(c) 2007 GeoTrust Inc. - For authorized use only -# Label: "GeoTrust Primary Certification Authority - G2" -# Serial: 80682863203381065782177908751794619243 -# MD5 Fingerprint: 01:5e:d8:6b:bd:6f:3d:8e:a1:31:f8:12:e0:98:73:6a -# SHA1 Fingerprint: 8d:17:84:d5:37:f3:03:7d:ec:70:fe:57:8b:51:9a:99:e6:10:d7:b0 -# SHA256 Fingerprint: 5e:db:7a:c4:3b:82:a0:6a:87:61:e8:d7:be:49:79:eb:f2:61:1f:7d:d7:9b:f9:1c:1c:6b:56:6a:21:9e:d7:66 ------BEGIN CERTIFICATE----- -MIICrjCCAjWgAwIBAgIQPLL0SAoA4v7rJDteYD7DazAKBggqhkjOPQQDAzCBmDEL -MAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChj -KSAyMDA3IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2 -MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0 -eSAtIEcyMB4XDTA3MTEwNTAwMDAwMFoXDTM4MDExODIzNTk1OVowgZgxCzAJBgNV -BAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykgMjAw -NyBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNV -BAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBH -MjB2MBAGByqGSM49AgEGBSuBBAAiA2IABBWx6P0DFUPlrOuHNxFi79KDNlJ9RVcL -So17VDs6bl8VAsBQps8lL33KSLjHUGMcKiEIfJo22Av+0SbFWDEwKCXzXV2juLal -tJLtbCyf691DiaI8S0iRHVDsJt/WYC69IaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO -BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBVfNVdRVfslsq0DafwBo/q+EVXVMAoG -CCqGSM49BAMDA2cAMGQCMGSWWaboCd6LuvpaiIjwH5HTRqjySkwCY/tsXzjbLkGT -qQ7mndwxHLKgpxgceeHHNgIwOlavmnRs9vuD4DPTCF+hnMJbn0bWtsuRBmOiBucz -rD6ogRLQy7rQkgu2npaqBA+K ------END CERTIFICATE----- - -# Issuer: CN=VeriSign Universal Root Certification Authority O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2008 VeriSign, Inc. - For authorized use only -# Subject: CN=VeriSign Universal Root Certification Authority O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2008 VeriSign, Inc. - For authorized use only -# Label: "VeriSign Universal Root Certification Authority" -# Serial: 85209574734084581917763752644031726877 -# MD5 Fingerprint: 8e:ad:b5:01:aa:4d:81:e4:8c:1d:d1:e1:14:00:95:19 -# SHA1 Fingerprint: 36:79:ca:35:66:87:72:30:4d:30:a5:fb:87:3b:0f:a7:7b:b7:0d:54 -# SHA256 Fingerprint: 23:99:56:11:27:a5:71:25:de:8c:ef:ea:61:0d:df:2f:a0:78:b5:c8:06:7f:4e:82:82:90:bf:b8:60:e8:4b:3c ------BEGIN CERTIFICATE----- -MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCB -vTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL -ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJp -U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MTgwNgYDVQQDEy9W -ZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe -Fw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJVUzEX -MBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0 -IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9y -IGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNh -bCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF -AAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj1mCOkdeQmIN65lgZOIzF -9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGPMiJhgsWH -H26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+H -LL729fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN -/BMReYTtXlT2NJ8IAfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPT -rJ9VAMf2CGqUuV/c4DPxhGD5WycRtPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1Ud -EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0GCCsGAQUFBwEMBGEwX6FdoFsw -WTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2Oa8PPgGrUSBgs -exkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud -DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4 -sAPmLGd75JR3Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+ -seQxIcaBlVZaDrHC1LGmWazxY8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz -4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTxP/jgdFcrGJ2BtMQo2pSXpXDrrB2+ -BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+PwGZsY6rp2aQW9IHR -lRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4mJO3 -7M2CYfE45k+XmCpajQ== ------END CERTIFICATE----- - -# Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G4 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2007 VeriSign, Inc. - For authorized use only -# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G4 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2007 VeriSign, Inc. - For authorized use only -# Label: "VeriSign Class 3 Public Primary Certification Authority - G4" -# Serial: 63143484348153506665311985501458640051 -# MD5 Fingerprint: 3a:52:e1:e7:fd:6f:3a:e3:6f:f3:6f:99:1b:f9:22:41 -# SHA1 Fingerprint: 22:d5:d8:df:8f:02:31:d1:8d:f7:9d:b7:cf:8a:2d:64:c9:3f:6c:3a -# SHA256 Fingerprint: 69:dd:d7:ea:90:bb:57:c9:3e:13:5d:c8:5e:a6:fc:d5:48:0b:60:32:39:bd:c4:54:fc:75:8b:2a:26:cf:7f:79 ------BEGIN CERTIFICATE----- -MIIDhDCCAwqgAwIBAgIQL4D+I4wOIg9IZxIokYesszAKBggqhkjOPQQDAzCByjEL -MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW -ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2ln -biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp -U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y -aXR5IC0gRzQwHhcNMDcxMTA1MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCByjELMAkG -A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJp -U2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwg -SW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2ln -biBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5 -IC0gRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASnVnp8Utpkmw4tXNherJI9/gHm -GUo9FANL+mAnINmDiWn6VMaaGF5VKmTeBvaNSjutEDxlPZCIBIngMGGzrl0Bp3ve -fLK+ymVhAIau2o970ImtTR1ZmkGxvEeA3J5iw/mjgbIwga8wDwYDVR0TAQH/BAUw -AwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJ -aW1hZ2UvZ2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYj -aHR0cDovL2xvZ28udmVyaXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFLMW -kf3upm7ktS5Jj4d4gYDs5bG1MAoGCCqGSM49BAMDA2gAMGUCMGYhDBgmYFo4e1ZC -4Kf8NoRRkSAsdk1DPcQdhCPQrNZ8NQbOzWm9kA3bbEhCHQ6qQgIxAJw9SDkjOVga -FRJZap7v1VmyHVIsmXHNxynfGyphe3HR3vPA5Q06Sqotp9iGKt0uEA== ------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=TC TrustCenter Universal CA III O=TC TrustCenter GmbH OU=TC TrustCenter Universal CA -# Subject: CN=TC TrustCenter Universal CA III O=TC TrustCenter GmbH OU=TC TrustCenter Universal CA -# Label: "TC TrustCenter Universal CA III" -# Serial: 2010889993983507346460533407902964 -# MD5 Fingerprint: 9f:dd:db:ab:ff:8e:ff:45:21:5f:f0:6c:9d:8f:fe:2b -# SHA1 Fingerprint: 96:56:cd:7b:57:96:98:95:d0:e1:41:46:68:06:fb:b8:c6:11:06:87 -# SHA256 Fingerprint: 30:9b:4a:87:f6:ca:56:c9:31:69:aa:a9:9c:6d:98:88:54:d7:89:2b:d5:43:7e:2d:07:b2:9c:be:da:55:d3:5d ------BEGIN CERTIFICATE----- -MIID4TCCAsmgAwIBAgIOYyUAAQACFI0zFQLkbPQwDQYJKoZIhvcNAQEFBQAwezEL -MAkGA1UEBhMCREUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxJDAiBgNV -BAsTG1RDIFRydXN0Q2VudGVyIFVuaXZlcnNhbCBDQTEoMCYGA1UEAxMfVEMgVHJ1 -c3RDZW50ZXIgVW5pdmVyc2FsIENBIElJSTAeFw0wOTA5MDkwODE1MjdaFw0yOTEy -MzEyMzU5NTlaMHsxCzAJBgNVBAYTAkRFMRwwGgYDVQQKExNUQyBUcnVzdENlbnRl -ciBHbWJIMSQwIgYDVQQLExtUQyBUcnVzdENlbnRlciBVbml2ZXJzYWwgQ0ExKDAm -BgNVBAMTH1RDIFRydXN0Q2VudGVyIFVuaXZlcnNhbCBDQSBJSUkwggEiMA0GCSqG -SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDC2pxisLlxErALyBpXsq6DFJmzNEubkKLF -5+cvAqBNLaT6hdqbJYUtQCggbergvbFIgyIpRJ9Og+41URNzdNW88jBmlFPAQDYv -DIRlzg9uwliT6CwLOunBjvvya8o84pxOjuT5fdMnnxvVZ3iHLX8LR7PH6MlIfK8v -zArZQe+f/prhsq75U7Xl6UafYOPfjdN/+5Z+s7Vy+EutCHnNaYlAJ/Uqwa1D7KRT -yGG299J5KmcYdkhtWyUB0SbFt1dpIxVbYYqt8Bst2a9c8SaQaanVDED1M4BDj5yj -dipFtK+/fz6HP3bFzSreIMUWWMv5G/UPyw0RUmS40nZid4PxWJ//AgMBAAGjYzBh -MB8GA1UdIwQYMBaAFFbn4VslQ4Dg9ozhcbyO5YAvxEjiMA8GA1UdEwEB/wQFMAMB -Af8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRW5+FbJUOA4PaM4XG8juWAL8RI -4jANBgkqhkiG9w0BAQUFAAOCAQEAg8ev6n9NCjw5sWi+e22JLumzCecYV42Fmhfz -dkJQEw/HkG8zrcVJYCtsSVgZ1OK+t7+rSbyUyKu+KGwWaODIl0YgoGhnYIg5IFHY -aAERzqf2EQf27OysGh+yZm5WZ2B6dF7AbZc2rrUNXWZzwCUyRdhKBgePxLcHsU0G -DeGl6/R1yrqc0L2z0zIkTO5+4nYES0lT2PLpVDP85XEfPRRclkvxOvIAu2y0+pZV -CIgJwcyRGSmwIC3/yzikQOEXvnlhgP8HA4ZMTnsGnxGGjYnuJ8Tb4rwZjgvDwxPH -LQNjO9Po5KIqwoIIlBZU8O8fJ5AluA0OKBtHd0e9HKgl8ZS0Zg== ------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=StartCom Certification Authority O=StartCom Ltd. OU=Secure Digital Certificate Signing -# Subject: CN=StartCom Certification Authority O=StartCom Ltd. OU=Secure Digital Certificate Signing -# Label: "StartCom Certification Authority" -# Serial: 45 -# MD5 Fingerprint: c9:3b:0d:84:41:fc:a4:76:79:23:08:57:de:10:19:16 -# SHA1 Fingerprint: a3:f1:33:3f:e2:42:bf:cf:c5:d1:4e:8f:39:42:98:40:68:10:d1:a0 -# SHA256 Fingerprint: e1:78:90:ee:09:a3:fb:f4:f4:8b:9c:41:4a:17:d6:37:b7:a5:06:47:e9:bc:75:23:22:72:7f:cc:17:42:a9:11 ------BEGIN CERTIFICATE----- -MIIHhzCCBW+gAwIBAgIBLTANBgkqhkiG9w0BAQsFADB9MQswCQYDVQQGEwJJTDEW -MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg -Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh -dGlvbiBBdXRob3JpdHkwHhcNMDYwOTE3MTk0NjM3WhcNMzYwOTE3MTk0NjM2WjB9 -MQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMi -U2VjdXJlIERpZ2l0YWwgQ2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3Rh -cnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUA -A4ICDwAwggIKAoICAQDBiNsJvGxGfHiflXu1M5DycmLWwTYgIiRezul38kMKogZk -pMyONvg45iPwbm2xPN1yo4UcodM9tDMr0y+v/uqwQVlntsQGfQqedIXWeUyAN3rf -OQVSWff0G0ZDpNKFhdLDcfN1YjS6LIp/Ho/u7TTQEceWzVI9ujPW3U3eCztKS5/C -Ji/6tRYccjV3yjxd5srhJosaNnZcAdt0FCX+7bWgiA/deMotHweXMAEtcnn6RtYT -Kqi5pquDSR3l8u/d5AGOGAqPY1MWhWKpDhk6zLVmpsJrdAfkK+F2PrRt2PZE4XNi -HzvEvqBTViVsUQn3qqvKv3b9bZvzndu/PWa8DFaqr5hIlTpL36dYUNk4dalb6kMM -Av+Z6+hsTXBbKWWc3apdzK8BMewM69KN6Oqce+Zu9ydmDBpI125C4z/eIT574Q1w -+2OqqGwaVLRcJXrJosmLFqa7LH4XXgVNWG4SHQHuEhANxjJ/GP/89PrNbpHoNkm+ -Gkhpi8KWTRoSsmkXwQqQ1vp5Iki/untp+HDH+no32NgN0nZPV/+Qt+OR0t3vwmC3 -Zzrd/qqc8NSLf3Iizsafl7b4r4qgEKjZ+xjGtrVcUjyJthkqcwEKDwOzEmDyei+B -26Nu/yYwl/WL3YlXtq09s68rxbd2AvCl1iuahhQqcvbjM4xdCUsT37uMdBNSSwID -AQABo4ICEDCCAgwwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD -VR0OBBYEFE4L7xqkQFulF2mHMMo0aEPQQa7yMB8GA1UdIwQYMBaAFE4L7xqkQFul -F2mHMMo0aEPQQa7yMIIBWgYDVR0gBIIBUTCCAU0wggFJBgsrBgEEAYG1NwEBATCC -ATgwLgYIKwYBBQUHAgEWImh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL3BvbGljeS5w -ZGYwNAYIKwYBBQUHAgEWKGh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL2ludGVybWVk -aWF0ZS5wZGYwgc8GCCsGAQUFBwICMIHCMCcWIFN0YXJ0IENvbW1lcmNpYWwgKFN0 -YXJ0Q29tKSBMdGQuMAMCAQEagZZMaW1pdGVkIExpYWJpbGl0eSwgcmVhZCB0aGUg -c2VjdGlvbiAqTGVnYWwgTGltaXRhdGlvbnMqIG9mIHRoZSBTdGFydENvbSBDZXJ0 -aWZpY2F0aW9uIEF1dGhvcml0eSBQb2xpY3kgYXZhaWxhYmxlIGF0IGh0dHA6Ly93 -d3cuc3RhcnRzc2wuY29tL3BvbGljeS5wZGYwEQYJYIZIAYb4QgEBBAQDAgAHMDgG -CWCGSAGG+EIBDQQrFilTdGFydENvbSBGcmVlIFNTTCBDZXJ0aWZpY2F0aW9uIEF1 -dGhvcml0eTANBgkqhkiG9w0BAQsFAAOCAgEAjo/n3JR5fPGFf59Jb2vKXfuM/gTF -wWLRfUKKvFO3lANmMD+x5wqnUCBVJX92ehQN6wQOQOY+2IirByeDqXWmN3PH/UvS -Ta0XQMhGvjt/UfzDtgUx3M2FIk5xt/JxXrAaxrqTi3iSSoX4eA+D/i+tLPfkpLst -0OcNOrg+zvZ49q5HJMqjNTbOx8aHmNrs++myziebiMMEofYLWWivydsQD032ZGNc -pRJvkrKTlMeIFw6Ttn5ii5B/q06f/ON1FE8qMt9bDeD1e5MNq6HPh+GlBEXoPBKl -CcWw0bdT82AUuoVpaiF8H3VhFyAXe2w7QSlc4axa0c2Mm+tgHRns9+Ww2vl5GKVF -P0lDV9LdJNUso/2RjSe15esUBppMeyG7Oq0wBhjA2MFrLH9ZXF2RsXAiV+uKa0hK -1Q8p7MZAwC+ITGgBF3f0JBlPvfrhsiAhS90a2Cl9qrjeVOwhVYBsHvUwyKMQ5bLm -KhQxw4UtjJixhlpPiVktucf3HMiKf8CdBUrmQk9io20ppB+Fq9vlgcitKj1MXVuE -JnHEhV5xJMqlG2zYYdMa4FTbzrqpMrUi9nNBCV24F10OD5mQ1kfabwo6YigUZ4LZ -8dCAWZvLMdibD4x3TrVoivJs9iQOLWxwxXPR3hTQcY+203sC9uO41Alua551hDnm -fyWl8kgAwKQB2j8= ------END CERTIFICATE----- - -# Issuer: CN=StartCom Certification Authority G2 O=StartCom Ltd. -# Subject: CN=StartCom Certification Authority G2 O=StartCom Ltd. -# Label: "StartCom Certification Authority G2" -# Serial: 59 -# MD5 Fingerprint: 78:4b:fb:9e:64:82:0a:d3:b8:4c:62:f3:64:f2:90:64 -# SHA1 Fingerprint: 31:f1:fd:68:22:63:20:ee:c6:3b:3f:9d:ea:4a:3e:53:7c:7c:39:17 -# SHA256 Fingerprint: c7:ba:65:67:de:93:a7:98:ae:1f:aa:79:1e:71:2d:37:8f:ae:1f:93:c4:39:7f:ea:44:1b:b7:cb:e6:fd:59:95 ------BEGIN CERTIFICATE----- -MIIFYzCCA0ugAwIBAgIBOzANBgkqhkiG9w0BAQsFADBTMQswCQYDVQQGEwJJTDEW -MBQGA1UEChMNU3RhcnRDb20gTHRkLjEsMCoGA1UEAxMjU3RhcnRDb20gQ2VydGlm -aWNhdGlvbiBBdXRob3JpdHkgRzIwHhcNMTAwMTAxMDEwMDAxWhcNMzkxMjMxMjM1 -OTAxWjBTMQswCQYDVQQGEwJJTDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjEsMCoG -A1UEAxMjU3RhcnRDb20gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgRzIwggIiMA0G -CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2iTZbB7cgNr2Cu+EWIAOVeq8Oo1XJ -JZlKxdBWQYeQTSFgpBSHO839sj60ZwNq7eEPS8CRhXBF4EKe3ikj1AENoBB5uNsD -vfOpL9HG4A/LnooUCri99lZi8cVytjIl2bLzvWXFDSxu1ZJvGIsAQRSCb0AgJnoo -D/Uefyf3lLE3PbfHkffiAez9lInhzG7TNtYKGXmu1zSCZf98Qru23QumNK9LYP5/ -Q0kGi4xDuFby2X8hQxfqp0iVAXV16iulQ5XqFYSdCI0mblWbq9zSOdIxHWDirMxW -RST1HFSr7obdljKF+ExP6JV2tgXdNiNnvP8V4so75qbsO+wmETRIjfaAKxojAuuK -HDp2KntWFhxyKrOq42ClAJ8Em+JvHhRYW6Vsi1g8w7pOOlz34ZYrPu8HvKTlXcxN -nw3h3Kq74W4a7I/htkxNeXJdFzULHdfBR9qWJODQcqhaX2YtENwvKhOuJv4KHBnM -0D4LnMgJLvlblnpHnOl68wVQdJVznjAJ85eCXuaPOQgeWeU1FEIT/wCc976qUM/i -UUjXuG+v+E5+M5iSFGI6dWPPe/regjupuznixL0sAA7IF6wT700ljtizkC+p2il9 -Ha90OrInwMEePnWjFqmveiJdnxMaz6eg6+OGCtP95paV1yPIN93EfKo2rJgaErHg -TuixO/XWb/Ew1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE -AwIBBjAdBgNVHQ4EFgQUS8W0QGutHLOlHGVuRjaJhwUMDrYwDQYJKoZIhvcNAQEL -BQADggIBAHNXPyzVlTJ+N9uWkusZXn5T50HsEbZH77Xe7XRcxfGOSeD8bpkTzZ+K -2s06Ctg6Wgk/XzTQLwPSZh0avZyQN8gMjgdalEVGKua+etqhqaRpEpKwfTbURIfX -UfEpY9Z1zRbkJ4kd+MIySP3bmdCPX1R0zKxnNBFi2QwKN4fRoxdIjtIXHfbX/dtl -6/2o1PXWT6RbdejF0mCy2wl+JYt7ulKSnj7oxXehPOBKc2thz4bcQ///If4jXSRK -9dNtD2IEBVeC2m6kMyV5Sy5UGYvMLD0w6dEG/+gyRr61M3Z3qAFdlsHB1b6uJcDJ -HgoJIIihDsnzb02CVAAgp9KP5DlUFy6NHrgbuxu9mk47EDTcnIhT76IxW1hPkWLI -wpqazRVdOKnWvvgTtZ8SafJQYqz7Fzf07rh1Z2AQ+4NQ+US1dZxAF7L+/XldblhY -XzD8AK6vM8EOTmy6p6ahfzLbOOCxchcKK5HsamMm7YnUeMx0HgX4a/6ManY5Ka5l -IxKVCCIcl85bBu4M4ru8H0ST9tg4RQUh7eStqxK2A6RCLi3ECToDZ2mEmuFZkIoo -hdVddLHRDiBYmxOlsGOm7XtH/UVVMKTumtTm4ofvmMkyghEpIrwACjFeLQ/Ajulr -so8uBtjRkcfGEvRM/TAXw8HaOFvjqermobp573PYtlNXLfbQ4ddI ------END CERTIFICATE----- - -# Issuer: CN=DigiCert Global Root G2, OU=www.digicert.com, O=DigiCert Inc, C=US -# Subject: CN=DigiCert Global Root G2, OU=www.digicert.com, O=DigiCert Inc, C=US -# Serial: 33af1e6a711a9a0bb2864b11d09fae5 -# 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: /C=US/O=Internet Security Research Group/CN=ISRG Root X1 -# Subject: /C=US/O=Internet Security Research Group/CN=ISRG Root X1 -# Serial: 8210CFB0D240E3594463E0BB63828B00 -# 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: /C=US/O=Internet Security Research Group/CN=ISRG Root X2 -# Subject: /C=US/O=Internet Security Research Group/CN=ISRG Root X2 -# Serial: 41D29DD172EAEEA780C12C6CE92F8752 -# 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----- diff --git a/shotgun_api3/lib/httplib2/python3/certs.py b/shotgun_api3/lib/httplib2/python3/certs.py deleted file mode 100644 index 59d1ffc70..000000000 --- a/shotgun_api3/lib/httplib2/python3/certs.py +++ /dev/null @@ -1,42 +0,0 @@ -"""Utilities for certificate management.""" - -import os - -certifi_available = False -certifi_where = None -try: - from certifi import where as certifi_where - certifi_available = True -except ImportError: - pass - -custom_ca_locater_available = False -custom_ca_locater_where = None -try: - from ca_certs_locater import get as custom_ca_locater_where - custom_ca_locater_available = True -except ImportError: - pass - - -BUILTIN_CA_CERTS = os.path.join( - os.path.dirname(os.path.abspath(__file__)), "cacerts.txt" -) - - -def where(): - env = os.environ.get("HTTPLIB2_CA_CERTS") - if env is not None: - if os.path.isfile(env): - return env - else: - raise RuntimeError("Environment variable HTTPLIB2_CA_CERTS not a valid file") - if custom_ca_locater_available: - return custom_ca_locater_where() - if certifi_available: - return certifi_where() - return BUILTIN_CA_CERTS - - -if __name__ == "__main__": - print(where()) diff --git a/shotgun_api3/lib/httplib2/python3/error.py b/shotgun_api3/lib/httplib2/python3/error.py deleted file mode 100644 index 0e68c12a8..000000000 --- a/shotgun_api3/lib/httplib2/python3/error.py +++ /dev/null @@ -1,48 +0,0 @@ -# All exceptions raised here derive from HttpLib2Error -class HttpLib2Error(Exception): - pass - - -# Some exceptions can be caught and optionally -# be turned back into responses. -class HttpLib2ErrorWithResponse(HttpLib2Error): - def __init__(self, desc, response, content): - self.response = response - self.content = content - HttpLib2Error.__init__(self, desc) - - -class RedirectMissingLocation(HttpLib2ErrorWithResponse): - pass - - -class RedirectLimit(HttpLib2ErrorWithResponse): - pass - - -class FailedToDecompressContent(HttpLib2ErrorWithResponse): - pass - - -class UnimplementedDigestAuthOptionError(HttpLib2ErrorWithResponse): - pass - - -class UnimplementedHmacDigestAuthOptionError(HttpLib2ErrorWithResponse): - pass - - -class MalformedHeader(HttpLib2Error): - pass - - -class RelativeURIError(HttpLib2Error): - pass - - -class ServerNotFoundError(HttpLib2Error): - pass - - -class ProxiesUnavailableError(HttpLib2Error): - pass diff --git a/shotgun_api3/lib/httplib2/python3/iri2uri.py b/shotgun_api3/lib/httplib2/python3/iri2uri.py deleted file mode 100644 index 86e361e62..000000000 --- a/shotgun_api3/lib/httplib2/python3/iri2uri.py +++ /dev/null @@ -1,124 +0,0 @@ -# -*- coding: utf-8 -*- -"""Converts an IRI to a URI.""" - -__author__ = "Joe Gregorio (joe@bitworking.org)" -__copyright__ = "Copyright 2006, Joe Gregorio" -__contributors__ = [] -__version__ = "1.0.0" -__license__ = "MIT" - -import urllib.parse - -# Convert an IRI to a URI following the rules in RFC 3987 -# -# The characters we need to enocde and escape are defined in the spec: -# -# iprivate = %xE000-F8FF / %xF0000-FFFFD / %x100000-10FFFD -# ucschar = %xA0-D7FF / %xF900-FDCF / %xFDF0-FFEF -# / %x10000-1FFFD / %x20000-2FFFD / %x30000-3FFFD -# / %x40000-4FFFD / %x50000-5FFFD / %x60000-6FFFD -# / %x70000-7FFFD / %x80000-8FFFD / %x90000-9FFFD -# / %xA0000-AFFFD / %xB0000-BFFFD / %xC0000-CFFFD -# / %xD0000-DFFFD / %xE1000-EFFFD - -escape_range = [ - (0xA0, 0xD7FF), - (0xE000, 0xF8FF), - (0xF900, 0xFDCF), - (0xFDF0, 0xFFEF), - (0x10000, 0x1FFFD), - (0x20000, 0x2FFFD), - (0x30000, 0x3FFFD), - (0x40000, 0x4FFFD), - (0x50000, 0x5FFFD), - (0x60000, 0x6FFFD), - (0x70000, 0x7FFFD), - (0x80000, 0x8FFFD), - (0x90000, 0x9FFFD), - (0xA0000, 0xAFFFD), - (0xB0000, 0xBFFFD), - (0xC0000, 0xCFFFD), - (0xD0000, 0xDFFFD), - (0xE1000, 0xEFFFD), - (0xF0000, 0xFFFFD), - (0x100000, 0x10FFFD), -] - - -def encode(c): - retval = c - i = ord(c) - for low, high in escape_range: - if i < low: - break - if i >= low and i <= high: - retval = "".join(["%%%2X" % o for o in c.encode("utf-8")]) - break - return retval - - -def iri2uri(uri): - """Convert an IRI to a URI. Note that IRIs must be - passed in a unicode strings. That is, do not utf-8 encode - the IRI before passing it into the function.""" - if isinstance(uri, str): - (scheme, authority, path, query, fragment) = urllib.parse.urlsplit(uri) - authority = authority.encode("idna").decode("utf-8") - # For each character in 'ucschar' or 'iprivate' - # 1. encode as utf-8 - # 2. then %-encode each octet of that utf-8 - uri = urllib.parse.urlunsplit((scheme, authority, path, query, fragment)) - uri = "".join([encode(c) for c in uri]) - return uri - - -if __name__ == "__main__": - import unittest - - class Test(unittest.TestCase): - def test_uris(self): - """Test that URIs are invariant under the transformation.""" - invariant = [ - "ftp://ftp.is.co.za/rfc/rfc1808.txt", - "http://www.ietf.org/rfc/rfc2396.txt", - "ldap://[2001:db8::7]/c=GB?objectClass?one", - "mailto:John.Doe@example.com", - "news:comp.infosystems.www.servers.unix", - "tel:+1-816-555-1212", - "telnet://192.0.2.16:80/", - "urn:oasis:names:specification:docbook:dtd:xml:4.1.2", - ] - for uri in invariant: - self.assertEqual(uri, iri2uri(uri)) - - def test_iri(self): - """Test that the right type of escaping is done for each part of the URI.""" - self.assertEqual( - "http://xn--o3h.com/%E2%98%84", - iri2uri("http://\N{COMET}.com/\N{COMET}"), - ) - self.assertEqual( - "http://bitworking.org/?fred=%E2%98%84", - iri2uri("http://bitworking.org/?fred=\N{COMET}"), - ) - self.assertEqual( - "http://bitworking.org/#%E2%98%84", - iri2uri("http://bitworking.org/#\N{COMET}"), - ) - self.assertEqual("#%E2%98%84", iri2uri("#\N{COMET}")) - self.assertEqual( - "/fred?bar=%E2%98%9A#%E2%98%84", - iri2uri("/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}"), - ) - self.assertEqual( - "/fred?bar=%E2%98%9A#%E2%98%84", - iri2uri(iri2uri("/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}")), - ) - self.assertNotEqual( - "/fred?bar=%E2%98%9A#%E2%98%84", - iri2uri( - "/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}".encode("utf-8") - ), - ) - - unittest.main() diff --git a/shotgun_api3/lib/httplib2/python3/socks.py b/shotgun_api3/lib/httplib2/python3/socks.py deleted file mode 100644 index cc68e634c..000000000 --- a/shotgun_api3/lib/httplib2/python3/socks.py +++ /dev/null @@ -1,518 +0,0 @@ -"""SocksiPy - Python SOCKS module. - -Version 1.00 - -Copyright 2006 Dan-Haim. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. -3. Neither the name of Dan Haim nor the names of his contributors may be used - to endorse or promote products derived from this software without specific - prior written permission. - -THIS SOFTWARE IS PROVIDED BY DAN HAIM "AS IS" AND ANY EXPRESS OR IMPLIED -WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO -EVENT SHALL DAN HAIM OR HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT -OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMANGE. - -This module provides a standard socket-like interface for Python -for tunneling connections through SOCKS proxies. - -Minor modifications made by Christopher Gilbert (http://motomastyle.com/) for -use in PyLoris (http://pyloris.sourceforge.net/). - -Minor modifications made by Mario Vilas (http://breakingcode.wordpress.com/) -mainly to merge bug fixes found in Sourceforge. -""" - -import base64 -import socket -import struct -import sys - -if getattr(socket, "socket", None) is None: - raise ImportError("socket.socket missing, proxy support unusable") - -PROXY_TYPE_SOCKS4 = 1 -PROXY_TYPE_SOCKS5 = 2 -PROXY_TYPE_HTTP = 3 -PROXY_TYPE_HTTP_NO_TUNNEL = 4 - -_defaultproxy = None -_orgsocket = socket.socket - - -class ProxyError(Exception): - pass - - -class GeneralProxyError(ProxyError): - pass - - -class Socks5AuthError(ProxyError): - pass - - -class Socks5Error(ProxyError): - pass - - -class Socks4Error(ProxyError): - pass - - -class HTTPError(ProxyError): - pass - - -_generalerrors = ( - "success", - "invalid data", - "not connected", - "not available", - "bad proxy type", - "bad input", -) - -_socks5errors = ( - "succeeded", - "general SOCKS server failure", - "connection not allowed by ruleset", - "Network unreachable", - "Host unreachable", - "Connection refused", - "TTL expired", - "Command not supported", - "Address type not supported", - "Unknown error", -) - -_socks5autherrors = ( - "succeeded", - "authentication is required", - "all offered authentication methods were rejected", - "unknown username or invalid password", - "unknown error", -) - -_socks4errors = ( - "request granted", - "request rejected or failed", - "request rejected because SOCKS server cannot connect to identd on the client", - "request rejected because the client program and identd report different " - "user-ids", - "unknown error", -) - - -def setdefaultproxy( - proxytype=None, addr=None, port=None, rdns=True, username=None, password=None -): - """setdefaultproxy(proxytype, addr[, port[, rdns[, username[, password]]]]) - Sets a default proxy which all further socksocket objects will use, - unless explicitly changed. - """ - global _defaultproxy - _defaultproxy = (proxytype, addr, port, rdns, username, password) - - -def wrapmodule(module): - """wrapmodule(module) - - Attempts to replace a module's socket library with a SOCKS socket. Must set - a default proxy using setdefaultproxy(...) first. - This will only work on modules that import socket directly into the - namespace; - most of the Python Standard Library falls into this category. - """ - if _defaultproxy != None: - module.socket.socket = socksocket - else: - raise GeneralProxyError((4, "no proxy specified")) - - -class socksocket(socket.socket): - """socksocket([family[, type[, proto]]]) -> socket object - Open a SOCKS enabled socket. The parameters are the same as - those of the standard socket init. In order for SOCKS to work, - you must specify family=AF_INET, type=SOCK_STREAM and proto=0. - """ - - def __init__( - self, family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0, _sock=None - ): - _orgsocket.__init__(self, family, type, proto, _sock) - if _defaultproxy != None: - self.__proxy = _defaultproxy - else: - self.__proxy = (None, None, None, None, None, None) - self.__proxysockname = None - self.__proxypeername = None - self.__httptunnel = True - - def __recvall(self, count): - """__recvall(count) -> data - Receive EXACTLY the number of bytes requested from the socket. - Blocks until the required number of bytes have been received. - """ - data = self.recv(count) - while len(data) < count: - d = self.recv(count - len(data)) - if not d: - raise GeneralProxyError((0, "connection closed unexpectedly")) - data = data + d - return data - - def sendall(self, content, *args): - """ override socket.socket.sendall method to rewrite the header - for non-tunneling proxies if needed - """ - if not self.__httptunnel: - content = self.__rewriteproxy(content) - return super(socksocket, self).sendall(content, *args) - - def __rewriteproxy(self, header): - """ rewrite HTTP request headers to support non-tunneling proxies - (i.e. those which do not support the CONNECT method). - This only works for HTTP (not HTTPS) since HTTPS requires tunneling. - """ - host, endpt = None, None - hdrs = header.split("\r\n") - for hdr in hdrs: - if hdr.lower().startswith("host:"): - host = hdr - elif hdr.lower().startswith("get") or hdr.lower().startswith("post"): - endpt = hdr - if host and endpt: - hdrs.remove(host) - hdrs.remove(endpt) - host = host.split(" ")[1] - endpt = endpt.split(" ") - if self.__proxy[4] != None and self.__proxy[5] != None: - hdrs.insert(0, self.__getauthheader()) - hdrs.insert(0, "Host: %s" % host) - hdrs.insert(0, "%s http://%s%s %s" % (endpt[0], host, endpt[1], endpt[2])) - return "\r\n".join(hdrs) - - def __getauthheader(self): - auth = self.__proxy[4] + b":" + self.__proxy[5] - return "Proxy-Authorization: Basic " + base64.b64encode(auth).decode() - - def setproxy( - self, - proxytype=None, - addr=None, - port=None, - rdns=True, - username=None, - password=None, - headers=None, - ): - """setproxy(proxytype, addr[, port[, rdns[, username[, password]]]]) - - Sets the proxy to be used. - proxytype - The type of the proxy to be used. Three types - are supported: PROXY_TYPE_SOCKS4 (including socks4a), - PROXY_TYPE_SOCKS5 and PROXY_TYPE_HTTP - addr - The address of the server (IP or DNS). - port - The port of the server. Defaults to 1080 for SOCKS - servers and 8080 for HTTP proxy servers. - rdns - Should DNS queries be preformed on the remote side - (rather than the local side). The default is True. - Note: This has no effect with SOCKS4 servers. - username - Username to authenticate with to the server. - The default is no authentication. - password - Password to authenticate with to the server. - Only relevant when username is also provided. - headers - Additional or modified headers for the proxy connect - request. - """ - self.__proxy = ( - proxytype, - addr, - port, - rdns, - username.encode() if username else None, - password.encode() if password else None, - headers, - ) - - def __negotiatesocks5(self, destaddr, destport): - """__negotiatesocks5(self,destaddr,destport) - Negotiates a connection through a SOCKS5 server. - """ - # First we'll send the authentication packages we support. - if (self.__proxy[4] != None) and (self.__proxy[5] != None): - # The username/password details were supplied to the - # setproxy method so we support the USERNAME/PASSWORD - # authentication (in addition to the standard none). - self.sendall(struct.pack("BBBB", 0x05, 0x02, 0x00, 0x02)) - else: - # No username/password were entered, therefore we - # only support connections with no authentication. - self.sendall(struct.pack("BBB", 0x05, 0x01, 0x00)) - # We'll receive the server's response to determine which - # method was selected - chosenauth = self.__recvall(2) - if chosenauth[0:1] != chr(0x05).encode(): - self.close() - raise GeneralProxyError((1, _generalerrors[1])) - # Check the chosen authentication method - if chosenauth[1:2] == chr(0x00).encode(): - # No authentication is required - pass - elif chosenauth[1:2] == chr(0x02).encode(): - # Okay, we need to perform a basic username/password - # authentication. - packet = bytearray() - packet.append(0x01) - packet.append(len(self.__proxy[4])) - packet.extend(self.__proxy[4]) - packet.append(len(self.__proxy[5])) - packet.extend(self.__proxy[5]) - self.sendall(packet) - authstat = self.__recvall(2) - if authstat[0:1] != chr(0x01).encode(): - # Bad response - self.close() - raise GeneralProxyError((1, _generalerrors[1])) - if authstat[1:2] != chr(0x00).encode(): - # Authentication failed - self.close() - raise Socks5AuthError((3, _socks5autherrors[3])) - # Authentication succeeded - else: - # Reaching here is always bad - self.close() - if chosenauth[1] == chr(0xFF).encode(): - raise Socks5AuthError((2, _socks5autherrors[2])) - else: - raise GeneralProxyError((1, _generalerrors[1])) - # Now we can request the actual connection - req = struct.pack("BBB", 0x05, 0x01, 0x00) - # If the given destination address is an IP address, we'll - # use the IPv4 address request even if remote resolving was specified. - try: - ipaddr = socket.inet_aton(destaddr) - req = req + chr(0x01).encode() + ipaddr - except socket.error: - # Well it's not an IP number, so it's probably a DNS name. - if self.__proxy[3]: - # Resolve remotely - ipaddr = None - req = ( - req - + chr(0x03).encode() - + chr(len(destaddr)).encode() - + destaddr.encode() - ) - else: - # Resolve locally - ipaddr = socket.inet_aton(socket.gethostbyname(destaddr)) - req = req + chr(0x01).encode() + ipaddr - req = req + struct.pack(">H", destport) - self.sendall(req) - # Get the response - resp = self.__recvall(4) - if resp[0:1] != chr(0x05).encode(): - self.close() - raise GeneralProxyError((1, _generalerrors[1])) - elif resp[1:2] != chr(0x00).encode(): - # Connection failed - self.close() - if ord(resp[1:2]) <= 8: - raise Socks5Error((ord(resp[1:2]), _socks5errors[ord(resp[1:2])])) - else: - raise Socks5Error((9, _socks5errors[9])) - # Get the bound address/port - elif resp[3:4] == chr(0x01).encode(): - boundaddr = self.__recvall(4) - elif resp[3:4] == chr(0x03).encode(): - resp = resp + self.recv(1) - boundaddr = self.__recvall(ord(resp[4:5])) - else: - self.close() - raise GeneralProxyError((1, _generalerrors[1])) - boundport = struct.unpack(">H", self.__recvall(2))[0] - self.__proxysockname = (boundaddr, boundport) - if ipaddr != None: - self.__proxypeername = (socket.inet_ntoa(ipaddr), destport) - else: - self.__proxypeername = (destaddr, destport) - - def getproxysockname(self): - """getsockname() -> address info - Returns the bound IP address and port number at the proxy. - """ - return self.__proxysockname - - def getproxypeername(self): - """getproxypeername() -> address info - Returns the IP and port number of the proxy. - """ - return _orgsocket.getpeername(self) - - def getpeername(self): - """getpeername() -> address info - Returns the IP address and port number of the destination - machine (note: getproxypeername returns the proxy) - """ - return self.__proxypeername - - def __negotiatesocks4(self, destaddr, destport): - """__negotiatesocks4(self,destaddr,destport) - Negotiates a connection through a SOCKS4 server. - """ - # Check if the destination address provided is an IP address - rmtrslv = False - try: - ipaddr = socket.inet_aton(destaddr) - except socket.error: - # It's a DNS name. Check where it should be resolved. - if self.__proxy[3]: - ipaddr = struct.pack("BBBB", 0x00, 0x00, 0x00, 0x01) - rmtrslv = True - else: - ipaddr = socket.inet_aton(socket.gethostbyname(destaddr)) - # Construct the request packet - req = struct.pack(">BBH", 0x04, 0x01, destport) + ipaddr - # The username parameter is considered userid for SOCKS4 - if self.__proxy[4] != None: - req = req + self.__proxy[4] - req = req + chr(0x00).encode() - # DNS name if remote resolving is required - # NOTE: This is actually an extension to the SOCKS4 protocol - # called SOCKS4A and may not be supported in all cases. - if rmtrslv: - req = req + destaddr + chr(0x00).encode() - self.sendall(req) - # Get the response from the server - resp = self.__recvall(8) - if resp[0:1] != chr(0x00).encode(): - # Bad data - self.close() - raise GeneralProxyError((1, _generalerrors[1])) - if resp[1:2] != chr(0x5A).encode(): - # Server returned an error - self.close() - if ord(resp[1:2]) in (91, 92, 93): - self.close() - raise Socks4Error((ord(resp[1:2]), _socks4errors[ord(resp[1:2]) - 90])) - else: - raise Socks4Error((94, _socks4errors[4])) - # Get the bound address/port - self.__proxysockname = ( - socket.inet_ntoa(resp[4:]), - struct.unpack(">H", resp[2:4])[0], - ) - if rmtrslv != None: - self.__proxypeername = (socket.inet_ntoa(ipaddr), destport) - else: - self.__proxypeername = (destaddr, destport) - - def __negotiatehttp(self, destaddr, destport): - """__negotiatehttp(self,destaddr,destport) - Negotiates a connection through an HTTP server. - """ - # If we need to resolve locally, we do this now - if not self.__proxy[3]: - addr = socket.gethostbyname(destaddr) - else: - addr = destaddr - headers = ["CONNECT ", addr, ":", str(destport), " HTTP/1.1\r\n"] - wrote_host_header = False - wrote_auth_header = False - if self.__proxy[6] != None: - for key, val in self.__proxy[6].iteritems(): - headers += [key, ": ", val, "\r\n"] - wrote_host_header = key.lower() == "host" - wrote_auth_header = key.lower() == "proxy-authorization" - if not wrote_host_header: - headers += ["Host: ", destaddr, "\r\n"] - if not wrote_auth_header: - if self.__proxy[4] != None and self.__proxy[5] != None: - headers += [self.__getauthheader(), "\r\n"] - headers.append("\r\n") - self.sendall("".join(headers).encode()) - # We read the response until we get the string "\r\n\r\n" - resp = self.recv(1) - while resp.find("\r\n\r\n".encode()) == -1: - resp = resp + self.recv(1) - # We just need the first line to check if the connection - # was successful - statusline = resp.splitlines()[0].split(" ".encode(), 2) - if statusline[0] not in ("HTTP/1.0".encode(), "HTTP/1.1".encode()): - self.close() - raise GeneralProxyError((1, _generalerrors[1])) - try: - statuscode = int(statusline[1]) - except ValueError: - self.close() - raise GeneralProxyError((1, _generalerrors[1])) - if statuscode != 200: - self.close() - raise HTTPError((statuscode, statusline[2])) - self.__proxysockname = ("0.0.0.0", 0) - self.__proxypeername = (addr, destport) - - def connect(self, destpair): - """connect(self, despair) - Connects to the specified destination through a proxy. - destpar - A tuple of the IP/DNS address and the port number. - (identical to socket's connect). - To select the proxy server use setproxy(). - """ - # Do a minimal input check first - if ( - (not type(destpair) in (list, tuple)) - or (len(destpair) < 2) - or (not isinstance(destpair[0], (str, bytes))) - or (type(destpair[1]) != int) - ): - raise GeneralProxyError((5, _generalerrors[5])) - if self.__proxy[0] == PROXY_TYPE_SOCKS5: - if self.__proxy[2] != None: - portnum = self.__proxy[2] - else: - portnum = 1080 - _orgsocket.connect(self, (self.__proxy[1], portnum)) - self.__negotiatesocks5(destpair[0], destpair[1]) - elif self.__proxy[0] == PROXY_TYPE_SOCKS4: - if self.__proxy[2] != None: - portnum = self.__proxy[2] - else: - portnum = 1080 - _orgsocket.connect(self, (self.__proxy[1], portnum)) - self.__negotiatesocks4(destpair[0], destpair[1]) - elif self.__proxy[0] == PROXY_TYPE_HTTP: - if self.__proxy[2] != None: - portnum = self.__proxy[2] - else: - portnum = 8080 - _orgsocket.connect(self, (self.__proxy[1], portnum)) - self.__negotiatehttp(destpair[0], destpair[1]) - elif self.__proxy[0] == PROXY_TYPE_HTTP_NO_TUNNEL: - if self.__proxy[2] != None: - portnum = self.__proxy[2] - else: - portnum = 8080 - _orgsocket.connect(self, (self.__proxy[1], portnum)) - if destpair[1] == 443: - self.__negotiatehttp(destpair[0], destpair[1]) - else: - self.__httptunnel = False - elif self.__proxy[0] == None: - _orgsocket.connect(self, (destpair[0], destpair[1])) - else: - raise GeneralProxyError((4, _generalerrors[4])) diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index 778393309..4390fea17 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -58,7 +58,7 @@ from .lib import six from .lib import sgsix from .lib import sgutils -from .lib.httplib2 import Http, ProxyInfo, socks, ssl_error_classes +from .lib.httplib2 import Http, ProxyInfo, socks from .lib.sgtimezone import SgTimezone @@ -3873,7 +3873,7 @@ def _make_call(self, verb, path, body, headers): # otherwise and will not re-attempt. # When we drop support of Python 2 and we will probably drop the # next except, we might want to remove this except too. - except ssl_error_classes as e: + except (ssl.SSLError, ssl.CertificateError) as e: # Test whether the exception is due to the fact that this is an older version of # Python that cannot validate certificates encrypted with SHA-2. If it is, then # fall back on disabling the certificate validation and try again - unless the diff --git a/tests/test_api.py b/tests/test_api.py index d42328f3c..f4cb42cf7 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -3434,10 +3434,6 @@ class TestLibImports(base.LiveTestBase): def test_import_httplib(self): """ Ensure that httplib2 is importable and objects are available - - This is important, because httplib2 imports switch between - the Python 2 and 3 compatible versions, and the module imports are - proxied to allow this. """ from shotgun_api3.lib import httplib2 @@ -3446,17 +3442,6 @@ def test_import_httplib(self): self.assertTrue(hasattr(httplib2, "Http")) self.assertTrue(isinstance(httplib2.Http, object)) - # Ensure that the version of httplib2 compatible with the current Python - # version was imported. - # (The last module name for __module__ should be either python2 or - # python3, depending on what has been imported. Make sure we got the - # right one.) - httplib2_compat_version = httplib2.Http.__module__.split(".")[-1] - if six.PY2: - self.assertEqual(httplib2_compat_version, "python2") - elif six.PY3: - self.assertTrue(httplib2_compat_version, "python3") - # Ensure that socks submodule is present and importable using a from # import -- this is a good indication that external httplib2 imports # from shotgun_api3 will work as expected. diff --git a/tests/test_unit.py b/tests/test_unit.py index 445d1fe07..42f882af4 100644 --- a/tests/test_unit.py +++ b/tests/test_unit.py @@ -11,13 +11,14 @@ # not expressly granted therein are reserved by Shotgun Software Inc. import os +import ssl import unittest from unittest import mock import urllib.request import urllib.error import shotgun_api3 as api -from shotgun_api3.lib.httplib2 import Http, ssl_error_classes +from shotgun_api3.lib.httplib2 import Http class TestShotgunInit(unittest.TestCase): @@ -771,7 +772,7 @@ def test_httplib(self): """ # First check that we get an error when trying to connect to a known dummy bad URL self.assertRaises( - ssl_error_classes, + (ssl.SSLError, ssl.CertificateError), self._check_url_with_sg_api_httplib2, self.bad_url, self.certs, diff --git a/update_httplib2.py b/update_httplib2.py index 30422e0c2..7124860e6 100755 --- a/update_httplib2.py +++ b/update_httplib2.py @@ -63,7 +63,7 @@ def sanitize_file(self, file_path): contents = contents.replace("from httplib2.", "from .") contents = contents.replace("from httplib2", "from .") contents = contents.replace( - "import pyparsing as pp", "from ... import pyparsing as pp" + "import pyparsing as pp", "from .. import pyparsing as pp" ) with open(file_path, "w") as f: @@ -73,8 +73,6 @@ def sanitize_file(self, file_path): def main(temp_path, repo_root, version): # Paths and file names httplib2_dir = repo_root / "shotgun_api3" / "lib" / "httplib2" - python2_dir = str(httplib2_dir / "python2") - python3_dir = str(httplib2_dir / "python3") file_name = f"{version}.zip" file_path = temp_path / file_name @@ -88,20 +86,15 @@ def main(temp_path, repo_root, version): unzipped_folder.mkdir() utilities.unzip_archive(file_path, file_name, unzipped_folder) - # Remove current httplib2/python2 and httplib2/python3 folders - utilities.remove_folder(python2_dir) - utilities.remove_folder(python3_dir) - # Removes the previous version of httplib2 - utilities.git_remove([str(python2_dir), str(python3_dir)]) + utilities.git_remove([str(httplib2_dir)]) + utilities.remove_folder(httplib2_dir) # Copies a new version into place. print("Copying new version of httplib2") root_folder = unzipped_folder / f"httplib2-{version[1:]}" - utilities.copy_folder(str(root_folder / "python2" / "httplib2"), python2_dir) - utilities.copy_folder(str(root_folder / "python3" / "httplib2"), python3_dir) - utilities.remove_folder(f"{python2_dir}/test") - utilities.remove_folder(f"{python3_dir}/test") + utilities.copy_folder(str(root_folder / "python3" / "httplib2"), httplib2_dir) + utilities.remove_folder(f"{httplib2_dir}/test") # Patches the httplib2 imports so they are relative instead of absolute. print("Patching imports") @@ -110,7 +103,7 @@ def main(temp_path, repo_root, version): # Adding files to the git repo. print("Adding to git") - subprocess.check_output(["git", "add", str(python2_dir), str(python3_dir)]) + subprocess.check_output(["git", "add", str(httplib2_dir)]) # nosec B607 if __name__ == "__main__": From 59b8f059fff88f7be0f9f237685b073e88334dbd Mon Sep 17 00:00:00 2001 From: Julien Langlois <16244608+julien-lang@users.noreply.github.com> Date: Wed, 10 Sep 2025 11:11:17 -0700 Subject: [PATCH 103/125] SG-38306 Python2 Removal - Part 7 - various (#404) --- README.md | 2 +- azure-pipelines-templates/run-tests.yml | 10 ++--- shotgun_api3/shotgun.py | 59 +++++++------------------ tests/base.py | 11 ++--- tests/test_api.py | 51 ++------------------- tests/test_client.py | 5 +-- 6 files changed, 31 insertions(+), 107 deletions(-) diff --git a/README.md b/README.md index 33e493821..f37c5dc21 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ Integration and unit tests are provided. - (Note: Running `pip install -r tests/ci_requirements.txt` will install this package) - A `tests/config` file (you can copy an example from `tests/example_config`). - Tests can be run individually like this: `nosetests --config="nose.cfg" tests/test_client.py` - - Make sure to not forget the `--config="nose.cfg"` option. This option tells nose to use our config file. This will exclude python 2- and 3-specific files in the `/lib` directory, preventing a failure from being reported by nose for compilation due to incompatible syntax in those files. + - Make sure to not forget the `--config="nose.cfg"` option. This option tells nose to use our config file. - `test_client` and `tests_unit` use mock server interaction and do not require a Flow Production Tracking instance to be available (no modifications to `tests/config` are necessary). - `test_api` and `test_api_long` *do* require a Flow Production Tracking instance, with a script key available for the tests. The server and script user values must be supplied in the `tests/config` file. The tests will add test data to your server based on information in your config. This data will be manipulated by the tests, and should not be used for other purposes. - To run all of the tests, use the shell script `run-tests`. diff --git a/azure-pipelines-templates/run-tests.yml b/azure-pipelines-templates/run-tests.yml index 6c60b39c8..c1a1a4ef8 100644 --- a/azure-pipelines-templates/run-tests.yml +++ b/azure-pipelines-templates/run-tests.yml @@ -33,9 +33,9 @@ parameters: jobs: # The job will be named after the OS and Azure will suffix the strategy to make it unique - # so we'll have a job name "Windows Python 2.7" for example. What's a strategy? Strategies are the - # name of the keys under the strategy.matrix scope. So for each OS we'll have " Python 2.7" and - # " Python 3.7". + # so we'll have a job name "Windows Python 3.9" for example. What's a strategy? Strategies are the + # name of the keys under the strategy.matrix scope. So for each OS we'll have " Python 3.9" and + # " Python 3.10". - job: ${{ parameters.name }} pool: vmImage: ${{ parameters.vm_image }} @@ -68,8 +68,8 @@ jobs: versionSpec: '$(python.version)' addToPath: True - # Install all dependencies needed for running the tests. This command is good for - # Python 2 and 3, but also for all OSes + # Install all dependencies needed for running the tests. This command is good + # for all OSes - script: | python -m pip install --upgrade pip setuptools wheel python -m pip install -r tests/ci_requirements.txt diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index 4390fea17..38d68bab7 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -35,7 +35,7 @@ import json import http.client # Used for secure file upload import http.cookiejar # used for attachment upload -import io # used for attachment upload +import io import logging import mimetypes import os @@ -49,6 +49,7 @@ import urllib.parse import urllib.request import uuid # used for attachment upload +import xml.etree.ElementTree # Import Error and ResponseError (even though they're unused in this file) since they need # to be exposed as part of the API. @@ -56,7 +57,6 @@ # Python 2/3 compatibility from .lib import six -from .lib import sgsix from .lib import sgutils from .lib.httplib2 import Http, ProxyInfo, socks from .lib.sgtimezone import SgTimezone @@ -329,7 +329,7 @@ class ClientCapabilities(object): ``windows``, or ``None`` (if the current platform couldn't be determined). :ivar str local_path_field: The PTR field used for local file paths. This is calculated using the value of ``platform``. Ex. ``local_path_mac``. - :ivar str py_version: Simple version of Python executable as a string. Eg. ``2.7``. + :ivar str py_version: Simple version of Python executable as a string. Eg. ``3.9``. :ivar str ssl_version: Version of OpenSSL installed. Eg. ``OpenSSL 1.0.2g 1 Mar 2016``. This info is only available in Python 2.7+ if the ssl module was imported successfully. Defaults to ``unknown`` @@ -567,18 +567,6 @@ def __init__( :class:`~shotgun_api3.MissingTwoFactorAuthenticationFault` will be raised if the ``auth_token`` is invalid. .. todo: Add this info to the Authentication section of the docs - - .. note:: A note about proxy connections: If you are using Python <= v2.6.2, HTTPS - connections through a proxy server will not work due to a bug in the :mod:`urllib2` - library (see http://bugs.python.org/issue1424152). This will affect upload and - download-related methods in the Shotgun API (eg. :meth:`~shotgun_api3.Shotgun.upload`, - :meth:`~shotgun_api3.Shotgun.upload_thumbnail`, - :meth:`~shotgun_api3.Shotgun.upload_filmstrip_thumbnail`, - :meth:`~shotgun_api3.Shotgun.download_attachment`. Normal CRUD methods for passing JSON - data should still work fine. If you cannot upgrade your Python installation, you can see - the patch merged into Python v2.6.3 (http://hg.python.org/cpython/rev/0f57b30a152f/) and - try and hack it into your installation but YMMV. For older versions of Python there - are other patches that were proposed in the bug report that may help you as well. """ # verify authentication arguments @@ -617,13 +605,7 @@ def __init__( if script_name is not None or api_key is not None: raise ValueError("cannot provide an auth_code with script_name/api_key") - # Can't use 'all' with python 2.4 - if ( - len( - [x for x in [session_token, script_name, api_key, login, password] if x] - ) - == 0 - ): + if not any([session_token, script_name, api_key, login, password]): if connect: raise ValueError( "must provide login/password, session_token or script_name/api_key" @@ -2879,8 +2861,7 @@ def download_attachment(self, attachment=False, file_path=None, attachment_id=No This parameter exists only for backwards compatibility for scripts specifying the parameter with keywords. :returns: If ``file_path`` is provided, returns the path to the file on disk. If - ``file_path`` is ``None``, returns the actual data of the file, as str in Python 2 or - bytes in Python 3. + ``file_path`` is ``None``, returns the actual data of the file, as bytes. :rtype: str | bytes """ # backwards compatibility when passed via keyword argument @@ -2941,12 +2922,13 @@ def download_attachment(self, attachment=False, file_path=None, attachment_id=No ] if body: - xml = "".join(body) - # Once python 2.4 support is not needed we can think about using - # elementtree. The doc is pretty small so this shouldn't be an issue. - match = re.search("(.*)", xml) - if match: - err += " - %s" % (match.group(1)) + try: + root = xml.etree.ElementTree.fromstring("".join(body)) + message_elem = root.find(".//Message") + if message_elem is not None and message_elem.text: + err = f"{err} - {message_elem.text}" + except xml.etree.ElementTree.ParseError: + err = f"{err}\n{''.join(body)}\n" elif e.code == 409 or e.code == 410: # we may be dealing with a file that is pending/failed a malware scan, e.g: # 409: This file is undergoing a malware scan, please try again in a few minutes @@ -4693,17 +4675,12 @@ class FormPostHandler(urllib.request.BaseHandler): handler_order = urllib.request.HTTPHandler.handler_order - 10 # needs to run first def http_request(self, request): - # get_data was removed in 3.4. since we're testing against 3.6 and - # 3.7, this should be sufficient. - if six.PY3: - data = request.data - else: - data = request.get_data() + data = request.data if data is not None and not isinstance(data, str): files = [] params = [] for key, value in data.items(): - if isinstance(value, sgsix.file_types): + if isinstance(value, io.IOBase): files.append((key, value)) else: params.append((key, value)) @@ -4714,12 +4691,8 @@ def http_request(self, request): boundary, data = self.encode(params, files) content_type = "multipart/form-data; boundary=%s" % boundary request.add_unredirected_header("Content-Type", content_type) - # add_data was removed in 3.4. since we're testing against 3.6 and - # 3.7, this should be sufficient. - if six.PY3: - request.data = data - else: - request.add_data(data) + request.data = data + return request def encode(self, params, files, boundary=None, buffer=None): diff --git a/tests/base.py b/tests/base.py index d1f138f47..3795d93af 100644 --- a/tests/base.py +++ b/tests/base.py @@ -176,13 +176,10 @@ def _mock_http(self, data, headers=None, status=None): return if not isinstance(data, str): - if six.PY2: - data = json.dumps(data, ensure_ascii=False, encoding="utf-8") - else: - data = json.dumps( - data, - ensure_ascii=False, - ) + data = json.dumps( + data, + ensure_ascii=False, + ) resp_headers = { "cache-control": "no-cache", diff --git a/tests/test_api.py b/tests/test_api.py index f4cb42cf7..85ed97285 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -32,11 +32,6 @@ from shotgun_api3.lib import six from shotgun_api3.lib.httplib2 import Http -# To mock the correct exception when testion on Python 2 and 3, use the -# ShotgunSSLError variable from sgsix that contains the appropriate exception -# class for the current Python version. -from shotgun_api3.lib.sgsix import ShotgunSSLError - import shotgun_api3 from . import base @@ -272,44 +267,6 @@ def test_upload_download(self): "sg_uploaded_movie", tag_list="monkeys, everywhere, send, help", ) - if six.PY2: - # In Python2, make sure that non-utf-8 encoded paths raise when they - # can't be converted to utf-8. For Python3, we'll skip these tests - # since string encoding is handled differently. - - # We need to touch the file we're going to test with first. We can't - # bundle a file with this filename in the repo due to some pip install - # problems on Windows. Note that the path below is utf-8 encoding of - # what we'll eventually encode as shift-jis. - file_path_s = os.path.join(this_dir, "./\xe3\x81\x94.shift-jis") - file_path_u = file_path_s.decode("utf-8") - - with open( - file_path_u if sys.platform.startswith("win") else file_path_s, "w" - ) as fh: - fh.write("This is just a test file with some random data in it.") - - self.assertRaises( - shotgun_api3.ShotgunError, - self.sg.upload, - "Version", - self.version["id"], - file_path_u.encode("shift-jis"), - "sg_uploaded_movie", - tag_list="monkeys, everywhere, send, help", - ) - - # But it should work in all cases if a unicode string is used. - self.sg.upload( - "Version", - self.version["id"], - file_path_u, - "sg_uploaded_movie", - tag_list="monkeys, everywhere, send, help", - ) - - # cleanup - os.remove(file_path_u) # cleanup os.remove(file_path) @@ -2265,7 +2222,7 @@ def my_side_effect2(*args, **kwargs): @unittest.mock.patch("shotgun_api3.shotgun.Http.request") def test_sha2_error(self, mock_request): # Simulate the exception raised with SHA-2 errors - mock_request.side_effect = ShotgunSSLError( + mock_request.side_effect = ssl.SSLError( "[Errno 1] _ssl.c:480: error:0D0C50A1:asn1 " "encoding routines:ASN1_item_verify: unknown message digest " "algorithm" @@ -2292,7 +2249,7 @@ def test_sha2_error(self, mock_request): try: self.sg.info() - except ShotgunSSLError: + except ssl.SSLError: # ensure the api has reset the values in the correct fallback behavior self.assertTrue(self.sg.config.no_ssl_validation) self.assertTrue(shotgun_api3.shotgun.NO_SSL_VALIDATION) @@ -2305,7 +2262,7 @@ def test_sha2_error(self, mock_request): @unittest.mock.patch("shotgun_api3.shotgun.Http.request") def test_sha2_error_with_strict(self, mock_request): # Simulate the exception raised with SHA-2 errors - mock_request.side_effect = ShotgunSSLError( + mock_request.side_effect = ssl.SSLError( "[Errno 1] _ssl.c:480: error:0D0C50A1:asn1 " "encoding routines:ASN1_item_verify: unknown message digest " "algorithm" @@ -2322,7 +2279,7 @@ def test_sha2_error_with_strict(self, mock_request): try: self.sg.info() - except ShotgunSSLError: + except ssl.SSLError: # ensure the api has NOT reset the values in the fallback behavior because we have # set the env variable to force validation self.assertFalse(self.sg.config.no_ssl_validation) diff --git a/tests/test_client.py b/tests/test_client.py index f99a79806..90fc94a9c 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -651,10 +651,7 @@ def _assert_decode_resonse(self, ensure_ascii, data): connect=False, ) - if six.PY3: - j = json.dumps(d, ensure_ascii=ensure_ascii) - else: - j = json.dumps(d, ensure_ascii=ensure_ascii, encoding="utf-8") + j = json.dumps(d, ensure_ascii=ensure_ascii) self.assertEqual(d, sg._decode_response(headers, j)) headers["content-type"] = "text/javascript" From 592377c2939a82cc662e1fdfe26fa54257bb4793 Mon Sep 17 00:00:00 2001 From: Julien Langlois <16244608+julien-lang@users.noreply.github.com> Date: Wed, 10 Sep 2025 11:50:18 -0700 Subject: [PATCH 104/125] SG-38306 Python2 Removal - Part 8 - Remove deprecated ensure_ascii parameter from SG object (#405) --- shotgun_api3/lib/mockgun/mockgun.py | 1 - shotgun_api3/shotgun.py | 33 ----------------------- tests/test_api.py | 42 ++++++++++------------------- tests/test_client.py | 1 - 4 files changed, 14 insertions(+), 63 deletions(-) diff --git a/shotgun_api3/lib/mockgun/mockgun.py b/shotgun_api3/lib/mockgun/mockgun.py index 18e4a142c..45d0b2aa5 100644 --- a/shotgun_api3/lib/mockgun/mockgun.py +++ b/shotgun_api3/lib/mockgun/mockgun.py @@ -177,7 +177,6 @@ def __init__(self, api_key=None, convert_datetimes_to_utc=True, http_proxy=None, - ensure_ascii=True, connect=True, ca_certs=None, login=None, diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index 38d68bab7..16c073d01 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -491,7 +491,6 @@ def __init__( api_key=None, convert_datetimes_to_utc=True, http_proxy=None, - ensure_ascii=True, connect=True, ca_certs=None, login=None, @@ -709,9 +708,6 @@ def __init__( {self.config.scheme: proxy_addr} ) - if ensure_ascii: - self._json_loads = self._json_loads_ascii - self.client_caps = ClientCapabilities() # this relies on self.client_caps being set first self.reset_user_agent() @@ -3981,35 +3977,6 @@ def _decode_response(self, headers, body): def _json_loads(self, body): return json.loads(body) - def _json_loads_ascii(self, body): - """ - See http://stackoverflow.com/questions/956867 - """ - - def _decode_list(lst): - newlist = [] - for i in lst: - if isinstance(i, str): - i = sgutils.ensure_str(i) - elif isinstance(i, list): - i = _decode_list(i) - newlist.append(i) - return newlist - - def _decode_dict(dct): - newdict = {} - for k, v in dct.items(): - if isinstance(k, str): - k = sgutils.ensure_str(k) - if isinstance(v, str): - v = sgutils.ensure_str(v) - elif isinstance(v, list): - v = _decode_list(v) - newdict[k] = v - return newdict - - return json.loads(body, object_hook=_decode_dict) - def _response_errors(self, sg_response): """ Raise any API errors specified in the response. diff --git a/tests/test_api.py b/tests/test_api.py index 85ed97285..cbbc115fe 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -29,7 +29,6 @@ import uuid import warnings -from shotgun_api3.lib import six from shotgun_api3.lib.httplib2 import Http import shotgun_api3 @@ -828,28 +827,24 @@ def test_summary_values(self): sorted(result["groups"], key=lambda x: x["group_name"]), groups ) - def test_ensure_ascii(self): - """test_ensure_ascii tests ensure_unicode flag.""" - sg_ascii = shotgun_api3.Shotgun( - self.config.server_url, ensure_ascii=True, **self.auth_args - ) + def test_json_dumps_default_ensure_ascii_disabled(self): + """Make sure SG'payload is using ensure_ascii for json dumps""" + sg = shotgun_api3.Shotgun(self.config.server_url, **self.auth_args) - result = sg_ascii.find_one( - "Note", [["id", "is", self.note["id"]]], fields=["content"] - ) - if six.PY2: - # In Python3 there isn't a separate unicode type. - self.assertFalse(_has_unicode(result)) + # Mock the _http_request method + sg._orig_http_request = sg._http_request + sg._http_request = unittest.mock.Mock(wraps=sg._orig_http_request) - def test_ensure_unicode(self): - """test_ensure_unicode tests ensure_unicode flag.""" - sg_unicode = shotgun_api3.Shotgun( - self.config.server_url, ensure_ascii=False, **self.auth_args + sg.find_one( + "Note", + [["content", "is", "Noëlご"]], # Force a non-ascii character ) - result = sg_unicode.find_one( - "Note", [["id", "is", self.note["id"]]], fields=["content"] + + sg._http_request.assert_called_once() + self.assertIn( + b"No\xc3\xabl\xe3\x81\x94", # utf-8 encoded version of Noëlご + sg._http_request.call_args.args[2], # Get the body of the request ) - self.assertTrue(_has_unicode(result)) def test_work_schedule(self): """test_work_schedule tests WorkDayRules api""" @@ -3409,15 +3404,6 @@ def test_import_httplib(self): self.assertTrue(hasattr(socks, "HTTPError")) -def _has_unicode(data): - for k, v in data.items(): - if isinstance(k, str): - return True - if isinstance(v, str): - return True - return False - - def _get_path(url): """Returns path component of a url without the sheme, host, query, anchor, or any other additional elements. diff --git a/tests/test_client.py b/tests/test_client.py index 90fc94a9c..65678cc18 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -647,7 +647,6 @@ def _assert_decode_resonse(self, ensure_ascii, data): self.config.script_name, self.config.api_key, http_proxy=self.config.http_proxy, - ensure_ascii=ensure_ascii, connect=False, ) From f7d076b1869d16c28587647c562965316bc18326 Mon Sep 17 00:00:00 2001 From: Eduardo Chauca <166560435+eduardoChaucaGallegos@users.noreply.github.com> Date: Wed, 10 Sep 2025 17:07:09 -0500 Subject: [PATCH 105/125] SG-38306 Python2 Removal - Part 9 - six module (#395) --- .coveragerc | 1 - .flake8 | 2 +- docs/cookbook/examples/ami_handler.rst | 1 - shotgun_api3/lib/README.md | 10 - shotgun_api3/lib/requirements.txt | 3 +- shotgun_api3/lib/sgsix.py | 87 --- shotgun_api3/lib/sgutils.py | 62 -- shotgun_api3/lib/six.py | 964 ------------------------- shotgun_api3/shotgun.py | 7 +- tests/base.py | 1 - tests/test_api_long.py | 1 - tests/test_client.py | 2 - 12 files changed, 4 insertions(+), 1137 deletions(-) delete mode 100644 shotgun_api3/lib/sgsix.py delete mode 100644 shotgun_api3/lib/sgutils.py delete mode 100644 shotgun_api3/lib/six.py diff --git a/.coveragerc b/.coveragerc index 97715b18d..21ce1f03d 100644 --- a/.coveragerc +++ b/.coveragerc @@ -16,6 +16,5 @@ source=shotgun_api3 omit= shotgun_api3/lib/httplib2/* - shotgun_api3/lib/six.py shotgun_api3/lib/certify/* shotgun_api3/lib/pyparsing.py diff --git a/.flake8 b/.flake8 index 4fc6605a0..50cb9acdc 100644 --- a/.flake8 +++ b/.flake8 @@ -10,4 +10,4 @@ [flake8] max-line-length = 120 -exclude = shotgun_api3/lib/httplib2/*,shotgun_api3/lib/six.py,tests/httplib2test.py +exclude = shotgun_api3/lib/httplib2/*,tests/httplib2test.py diff --git a/docs/cookbook/examples/ami_handler.rst b/docs/cookbook/examples/ami_handler.rst index 6b8f3384b..aee16f356 100644 --- a/docs/cookbook/examples/ami_handler.rst +++ b/docs/cookbook/examples/ami_handler.rst @@ -95,7 +95,6 @@ via ``POST``. If you're using a custom protocol the data is sent via ``GET``. # Imports # --------------------------------------------------------------------------------------------- import sys, os - import six import logging as logger # --------------------------------------------------------------------------------------------- diff --git a/shotgun_api3/lib/README.md b/shotgun_api3/lib/README.md index 7097b6c62..afdd28437 100644 --- a/shotgun_api3/lib/README.md +++ b/shotgun_api3/lib/README.md @@ -10,18 +10,8 @@ Some third-party modules are bundled with `python-api` inside lib. The version of `httplib2` bundled should be updated manually, however its version is included in the unused `shotgun_api3/lib/requirements.txt` to allow Github's automated CVE notifications to work. -### six - -Six is a Python 2/3 compatibility library. In python-api, it's used to make simultaneous support for Python on 2 and 3 easier to maintain and more readable, but allowing the use of common helper functions, unified interfaces for modules that changed, and variables to ease type comparisons. For more on six, see the [documentation](https://six.readthedocs.io/). - -The version of `six` bundled should be updated manually, however its version is included in the unused `shotgun_api3/lib/requirements.txt` to allow Github's automated CVE notifications to work. - ## Flow Production Tracking Modules -### sgsix - -`sgsix` is a module that contains extensions to `six`. These might be additional helper functions, variables, etc. that supplement six's functionality. It is intended that `sgsix` can be used within other packages that include or depend on the `python-api` package as well. - ### sgtimezone `sgtimezone` contains classes for easing the conversion between the server (UTC) timezone and client timezone. diff --git a/shotgun_api3/lib/requirements.txt b/shotgun_api3/lib/requirements.txt index f91a3ae19..a3bc11436 100644 --- a/shotgun_api3/lib/requirements.txt +++ b/shotgun_api3/lib/requirements.txt @@ -29,6 +29,5 @@ # This file is unused. It is left there so Github can warn us is a CVE is # released for our dependencies. httplib2==0.22.0 -six==1.13.0 -certifi==2025.7.14 +certifi==2024.7.4 pyparsing==2.4.7 diff --git a/shotgun_api3/lib/sgsix.py b/shotgun_api3/lib/sgsix.py deleted file mode 100644 index 6c2af1abc..000000000 --- a/shotgun_api3/lib/sgsix.py +++ /dev/null @@ -1,87 +0,0 @@ -""" - ----------------------------------------------------------------------------- - Copyright (c) 2009-2019, Shotgun Software Inc. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - - Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - - - Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - - Neither the name of the Shotgun Software Inc nor the names of its - contributors may be used to endorse or promote products derived from this - software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -""" - -# This module contains addtional functions and variables to supplement the six -# module for python 2/3 compatibility. - -from . import six -import io -import sys - -# For python 3, the `file` type no longer exists, and open() returns an -# io.IOBase instance. We add file_types to allow comparison across python -# versions. See https://stackoverflow.com/questions/36321030#36321030 -# -# This means that to test if a variable contains a file in both Python 2 and 3 -# you can use an isinstance test like: -# isinstance(value, sgsix.file_types) -if six.PY3: - file_types = (io.IOBase, ) -else: - file_types = (file, io.IOBase) # noqa warning for undefined `file` in python 3 - -# For python-api calls that result in an SSL error, the exception raised is -# different on Python 2 and 3. Store the approriate exception class in a -# variable to allow easier exception handling across Python 2/3. -if six.PY3: - import ssl - ShotgunSSLError = ssl.SSLError -else: - from .httplib2 import SSLHandshakeError - ShotgunSSLError = SSLHandshakeError - - -def normalize_platform(platform, python2=True): - """ - Normalize the return of sys.platform between Python 2 and 3. - - On Python 2 on linux hosts, sys.platform was 'linux' appended with the - current kernel version that Python was built on. In Python3, this was - changed and sys.platform now returns 'linux' regardless of the kernel version. - See https://bugs.python.org/issue12326 - This function will normalize platform strings to always conform to Python2 or - Python3 behavior. - - :param str platform: The platform string to normalize - :param bool python2: The python version behavior to target. If True, a - Python2-style platform string will be returned (i.e. 'linux2'), otherwise - the modern 'linux' platform string will be returned. - - :returns: The normalized platform string. - :rtype: str - """ - if python2: - return "linux2" if platform.startswith("linux") else platform - return "linux" if platform.startswith("linux") else platform - - -# sgsix.platform will mimick the python2 sys.platform behavior to ensure -# compatibility with existing comparisons and dict keys. -platform = normalize_platform(sys.platform) diff --git a/shotgun_api3/lib/sgutils.py b/shotgun_api3/lib/sgutils.py deleted file mode 100644 index 0d49e4b39..000000000 --- a/shotgun_api3/lib/sgutils.py +++ /dev/null @@ -1,62 +0,0 @@ -""" - ----------------------------------------------------------------------------- - Copyright (c) 2009-2024, Shotgun Software Inc. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - - Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - - - Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - - Neither the name of the Shotgun Software Inc nor the names of its - contributors may be used to endorse or promote products derived from this - software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -""" - - -def ensure_binary(s, encoding='utf-8', errors='strict'): - """ - Coerce **s** to bytes. - - - `str` -> encoded to `bytes` - - `bytes` -> `bytes` - """ - if isinstance(s, str): - return s.encode(encoding, errors) - elif isinstance(s, bytes): - return s - else: - raise TypeError(f"not expecting type '{type(s)}'") - - -def ensure_str(s, encoding='utf-8', errors='strict'): - """Coerce *s* to `str`. - - - `str` -> `str` - - `bytes` -> decoded to `str` - """ - if isinstance(s, str): - return s - - elif isinstance(s, bytes): - return s.decode(encoding, errors) - - raise TypeError(f"not expecting type '{type(s)}'") - - -ensure_text = ensure_str diff --git a/shotgun_api3/lib/six.py b/shotgun_api3/lib/six.py deleted file mode 100644 index b22d2e57d..000000000 --- a/shotgun_api3/lib/six.py +++ /dev/null @@ -1,964 +0,0 @@ -# Copyright (c) 2010-2019 Benjamin Peterson -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -"""Utilities for writing code that runs on Python 2 and 3""" - -from __future__ import absolute_import - -import functools -import itertools -import operator -import sys -import types - -__author__ = "Benjamin Peterson " -__version__ = "1.13.0" - - -# Useful for very coarse version differentiation. -PY2 = sys.version_info[0] == 2 -PY3 = sys.version_info[0] == 3 -PY34 = sys.version_info[0:2] >= (3, 4) -PY38 = sys.version_info[0:2] >= (3, 8) - -if PY3: - string_types = str, - integer_types = int, - class_types = type, - text_type = str - binary_type = bytes - - MAXSIZE = sys.maxsize -else: - string_types = basestring, - integer_types = (int, long) - class_types = (type, types.ClassType) - text_type = unicode - binary_type = str - - if sys.platform.startswith("java"): - # Jython always uses 32 bits. - MAXSIZE = int((1 << 31) - 1) - else: - # It's possible to have sizeof(long) != sizeof(Py_ssize_t). - class X(object): - - def __len__(self): - return 1 << 31 - try: - len(X()) - except OverflowError: - # 32-bit - MAXSIZE = int((1 << 31) - 1) - else: - # 64-bit - MAXSIZE = int((1 << 63) - 1) - del X - - -def _add_doc(func, doc): - """Add documentation to a function.""" - func.__doc__ = doc - - -def _import_module(name): - """Import module, returning the module after the last dot.""" - __import__(name) - return sys.modules[name] - - -class _LazyDescr(object): - - def __init__(self, name): - self.name = name - - def __get__(self, obj, tp): - result = self._resolve() - setattr(obj, self.name, result) # Invokes __set__. - try: - # This is a bit ugly, but it avoids running this again by - # removing this descriptor. - delattr(obj.__class__, self.name) - except AttributeError: - pass - return result - - -class MovedModule(_LazyDescr): - - def __init__(self, name, old, new=None): - super(MovedModule, self).__init__(name) - if PY3: - if new is None: - new = name - self.mod = new - else: - self.mod = old - - def _resolve(self): - return _import_module(self.mod) - - def __getattr__(self, attr): - _module = self._resolve() - value = getattr(_module, attr) - setattr(self, attr, value) - return value - - -class _LazyModule(types.ModuleType): - - def __init__(self, name): - super(_LazyModule, self).__init__(name) - self.__doc__ = self.__class__.__doc__ - - def __dir__(self): - attrs = ["__doc__", "__name__"] - attrs += [attr.name for attr in self._moved_attributes] - return attrs - - # Subclasses should override this - _moved_attributes = [] - - -class MovedAttribute(_LazyDescr): - - def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None): - super(MovedAttribute, self).__init__(name) - if PY3: - if new_mod is None: - new_mod = name - self.mod = new_mod - if new_attr is None: - if old_attr is None: - new_attr = name - else: - new_attr = old_attr - self.attr = new_attr - else: - self.mod = old_mod - if old_attr is None: - old_attr = name - self.attr = old_attr - - def _resolve(self): - module = _import_module(self.mod) - return getattr(module, self.attr) - - -class _SixMetaPathImporter(object): - - """ - A meta path importer to import six.moves and its submodules. - - This class implements a PEP302 finder and loader. It should be compatible - with Python 2.5 and all existing versions of Python3 - """ - - def __init__(self, six_module_name): - self.name = six_module_name - self.known_modules = {} - - def _add_module(self, mod, *fullnames): - for fullname in fullnames: - self.known_modules[self.name + "." + fullname] = mod - - def _get_module(self, fullname): - return self.known_modules[self.name + "." + fullname] - - def find_module(self, fullname, path=None): - if fullname in self.known_modules: - return self - return None - - def __get_module(self, fullname): - try: - return self.known_modules[fullname] - except KeyError: - raise ImportError("This loader does not know module " + fullname) - - def load_module(self, fullname): - try: - # in case of a reload - return sys.modules[fullname] - except KeyError: - pass - mod = self.__get_module(fullname) - if isinstance(mod, MovedModule): - mod = mod._resolve() - else: - mod.__loader__ = self - sys.modules[fullname] = mod - return mod - - def is_package(self, fullname): - """ - Return true, if the named module is a package. - - We need this method to get correct spec objects with - Python 3.4 (see PEP451) - """ - return hasattr(self.__get_module(fullname), "__path__") - - def get_code(self, fullname): - """Return None - - Required, if is_package is implemented""" - self.__get_module(fullname) # eventually raises ImportError - return None - get_source = get_code # same as get_code - -_importer = _SixMetaPathImporter(__name__) - - -class _MovedItems(_LazyModule): - - """Lazy loading of moved objects""" - __path__ = [] # mark as package - - -_moved_attributes = [ - MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), - MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"), - MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"), - MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"), - MovedAttribute("intern", "__builtin__", "sys"), - MovedAttribute("map", "itertools", "builtins", "imap", "map"), - MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"), - MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"), - MovedAttribute("getoutput", "commands", "subprocess"), - MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"), - MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"), - MovedAttribute("reduce", "__builtin__", "functools"), - MovedAttribute("shlex_quote", "pipes", "shlex", "quote"), - MovedAttribute("StringIO", "StringIO", "io"), - MovedAttribute("UserDict", "UserDict", "collections"), - MovedAttribute("UserList", "UserList", "collections"), - MovedAttribute("UserString", "UserString", "collections"), - MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"), - MovedAttribute("zip", "itertools", "builtins", "izip", "zip"), - MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"), - MovedModule("builtins", "__builtin__"), - MovedModule("configparser", "ConfigParser"), - MovedModule("collections_abc", "collections", "collections.abc" if sys.version_info >= (3, 3) else "collections"), - MovedModule("copyreg", "copy_reg"), - MovedModule("dbm_gnu", "gdbm", "dbm.gnu"), - MovedModule("dbm_ndbm", "dbm", "dbm.ndbm"), - MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"), - MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), - MovedModule("http_cookies", "Cookie", "http.cookies"), - MovedModule("html_entities", "htmlentitydefs", "html.entities"), - MovedModule("html_parser", "HTMLParser", "html.parser"), - MovedModule("http_client", "httplib", "http.client"), - MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"), - MovedModule("email_mime_image", "email.MIMEImage", "email.mime.image"), - MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"), - MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"), - MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"), - MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"), - MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"), - MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"), - MovedModule("cPickle", "cPickle", "pickle"), - MovedModule("queue", "Queue"), - MovedModule("reprlib", "repr"), - MovedModule("socketserver", "SocketServer"), - MovedModule("_thread", "thread", "_thread"), - MovedModule("tkinter", "Tkinter"), - MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"), - MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"), - MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"), - MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"), - MovedModule("tkinter_tix", "Tix", "tkinter.tix"), - MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"), - MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"), - MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"), - MovedModule("tkinter_colorchooser", "tkColorChooser", - "tkinter.colorchooser"), - MovedModule("tkinter_commondialog", "tkCommonDialog", - "tkinter.commondialog"), - MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"), - MovedModule("tkinter_font", "tkFont", "tkinter.font"), - MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"), - MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", - "tkinter.simpledialog"), - MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"), - MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"), - MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"), - MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"), - MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"), - MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"), -] -# Add windows specific modules. -if sys.platform == "win32": - _moved_attributes += [ - MovedModule("winreg", "_winreg"), - ] - -for attr in _moved_attributes: - setattr(_MovedItems, attr.name, attr) - if isinstance(attr, MovedModule): - _importer._add_module(attr, "moves." + attr.name) -del attr - -_MovedItems._moved_attributes = _moved_attributes - -moves = _MovedItems(__name__ + ".moves") -_importer._add_module(moves, "moves") - - -class Module_six_moves_urllib_parse(_LazyModule): - - """Lazy loading of moved objects in six.moves.urllib_parse""" - - -_urllib_parse_moved_attributes = [ - MovedAttribute("ParseResult", "urlparse", "urllib.parse"), - MovedAttribute("SplitResult", "urlparse", "urllib.parse"), - MovedAttribute("parse_qs", "urlparse", "urllib.parse"), - MovedAttribute("parse_qsl", "urlparse", "urllib.parse"), - MovedAttribute("urldefrag", "urlparse", "urllib.parse"), - MovedAttribute("urljoin", "urlparse", "urllib.parse"), - MovedAttribute("urlparse", "urlparse", "urllib.parse"), - MovedAttribute("urlsplit", "urlparse", "urllib.parse"), - MovedAttribute("urlunparse", "urlparse", "urllib.parse"), - MovedAttribute("urlunsplit", "urlparse", "urllib.parse"), - MovedAttribute("quote", "urllib", "urllib.parse"), - MovedAttribute("quote_plus", "urllib", "urllib.parse"), - MovedAttribute("unquote", "urllib", "urllib.parse"), - MovedAttribute("unquote_plus", "urllib", "urllib.parse"), - MovedAttribute("unquote_to_bytes", "urllib", "urllib.parse", "unquote", "unquote_to_bytes"), - MovedAttribute("urlencode", "urllib", "urllib.parse"), - MovedAttribute("splitquery", "urllib", "urllib.parse"), - MovedAttribute("splittag", "urllib", "urllib.parse"), - MovedAttribute("splituser", "urllib", "urllib.parse"), - MovedAttribute("splitvalue", "urllib", "urllib.parse"), - MovedAttribute("uses_fragment", "urlparse", "urllib.parse"), - MovedAttribute("uses_netloc", "urlparse", "urllib.parse"), - MovedAttribute("uses_params", "urlparse", "urllib.parse"), - MovedAttribute("uses_query", "urlparse", "urllib.parse"), - MovedAttribute("uses_relative", "urlparse", "urllib.parse"), -] -for attr in _urllib_parse_moved_attributes: - setattr(Module_six_moves_urllib_parse, attr.name, attr) -del attr - -Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes - -_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"), - "moves.urllib_parse", "moves.urllib.parse") - - -class Module_six_moves_urllib_error(_LazyModule): - - """Lazy loading of moved objects in six.moves.urllib_error""" - - -_urllib_error_moved_attributes = [ - MovedAttribute("URLError", "urllib2", "urllib.error"), - MovedAttribute("HTTPError", "urllib2", "urllib.error"), - MovedAttribute("ContentTooShortError", "urllib", "urllib.error"), -] -for attr in _urllib_error_moved_attributes: - setattr(Module_six_moves_urllib_error, attr.name, attr) -del attr - -Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes - -_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"), - "moves.urllib_error", "moves.urllib.error") - - -class Module_six_moves_urllib_request(_LazyModule): - - """Lazy loading of moved objects in six.moves.urllib_request""" - - -_urllib_request_moved_attributes = [ - MovedAttribute("urlopen", "urllib2", "urllib.request"), - MovedAttribute("install_opener", "urllib2", "urllib.request"), - MovedAttribute("build_opener", "urllib2", "urllib.request"), - MovedAttribute("pathname2url", "urllib", "urllib.request"), - MovedAttribute("url2pathname", "urllib", "urllib.request"), - MovedAttribute("getproxies", "urllib", "urllib.request"), - MovedAttribute("Request", "urllib2", "urllib.request"), - MovedAttribute("OpenerDirector", "urllib2", "urllib.request"), - MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"), - MovedAttribute("ProxyHandler", "urllib2", "urllib.request"), - MovedAttribute("BaseHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"), - MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"), - MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"), - MovedAttribute("FileHandler", "urllib2", "urllib.request"), - MovedAttribute("FTPHandler", "urllib2", "urllib.request"), - MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"), - MovedAttribute("UnknownHandler", "urllib2", "urllib.request"), - MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"), - MovedAttribute("urlretrieve", "urllib", "urllib.request"), - MovedAttribute("urlcleanup", "urllib", "urllib.request"), - MovedAttribute("URLopener", "urllib", "urllib.request"), - MovedAttribute("FancyURLopener", "urllib", "urllib.request"), - MovedAttribute("proxy_bypass", "urllib", "urllib.request"), - MovedAttribute("parse_http_list", "urllib2", "urllib.request"), - MovedAttribute("parse_keqv_list", "urllib2", "urllib.request"), -] -for attr in _urllib_request_moved_attributes: - setattr(Module_six_moves_urllib_request, attr.name, attr) -del attr - -Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes - -_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"), - "moves.urllib_request", "moves.urllib.request") - - -class Module_six_moves_urllib_response(_LazyModule): - - """Lazy loading of moved objects in six.moves.urllib_response""" - - -_urllib_response_moved_attributes = [ - MovedAttribute("addbase", "urllib", "urllib.response"), - MovedAttribute("addclosehook", "urllib", "urllib.response"), - MovedAttribute("addinfo", "urllib", "urllib.response"), - MovedAttribute("addinfourl", "urllib", "urllib.response"), -] -for attr in _urllib_response_moved_attributes: - setattr(Module_six_moves_urllib_response, attr.name, attr) -del attr - -Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes - -_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"), - "moves.urllib_response", "moves.urllib.response") - - -class Module_six_moves_urllib_robotparser(_LazyModule): - - """Lazy loading of moved objects in six.moves.urllib_robotparser""" - - -_urllib_robotparser_moved_attributes = [ - MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"), -] -for attr in _urllib_robotparser_moved_attributes: - setattr(Module_six_moves_urllib_robotparser, attr.name, attr) -del attr - -Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes - -_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"), - "moves.urllib_robotparser", "moves.urllib.robotparser") - - -class Module_six_moves_urllib(types.ModuleType): - - """Create a six.moves.urllib namespace that resembles the Python 3 namespace""" - __path__ = [] # mark as package - parse = _importer._get_module("moves.urllib_parse") - error = _importer._get_module("moves.urllib_error") - request = _importer._get_module("moves.urllib_request") - response = _importer._get_module("moves.urllib_response") - robotparser = _importer._get_module("moves.urllib_robotparser") - - def __dir__(self): - return ['parse', 'error', 'request', 'response', 'robotparser'] - -_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"), - "moves.urllib") - - -def add_move(move): - """Add an item to six.moves.""" - setattr(_MovedItems, move.name, move) - - -def remove_move(name): - """Remove item from six.moves.""" - try: - delattr(_MovedItems, name) - except AttributeError: - try: - del moves.__dict__[name] - except KeyError: - raise AttributeError("no such move, %r" % (name,)) - - -if PY3: - _meth_func = "__func__" - _meth_self = "__self__" - - _func_closure = "__closure__" - _func_code = "__code__" - _func_defaults = "__defaults__" - _func_globals = "__globals__" -else: - _meth_func = "im_func" - _meth_self = "im_self" - - _func_closure = "func_closure" - _func_code = "func_code" - _func_defaults = "func_defaults" - _func_globals = "func_globals" - - -try: - advance_iterator = next -except NameError: - def advance_iterator(it): - return it.next() -next = advance_iterator - - -try: - callable = callable -except NameError: - def callable(obj): - return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) - - -if PY3: - def get_unbound_function(unbound): - return unbound - - create_bound_method = types.MethodType - - def create_unbound_method(func, cls): - return func - - Iterator = object -else: - def get_unbound_function(unbound): - return unbound.im_func - - def create_bound_method(func, obj): - return types.MethodType(func, obj, obj.__class__) - - def create_unbound_method(func, cls): - return types.MethodType(func, None, cls) - - class Iterator(object): - - def next(self): - return type(self).__next__(self) - - callable = callable -_add_doc(get_unbound_function, - """Get the function out of a possibly unbound function""") - - -get_method_function = operator.attrgetter(_meth_func) -get_method_self = operator.attrgetter(_meth_self) -get_function_closure = operator.attrgetter(_func_closure) -get_function_code = operator.attrgetter(_func_code) -get_function_defaults = operator.attrgetter(_func_defaults) -get_function_globals = operator.attrgetter(_func_globals) - - -if PY3: - def iterkeys(d, **kw): - return iter(d.keys(**kw)) - - def itervalues(d, **kw): - return iter(d.values(**kw)) - - def iteritems(d, **kw): - return iter(d.items(**kw)) - - def iterlists(d, **kw): - return iter(d.lists(**kw)) - - viewkeys = operator.methodcaller("keys") - - viewvalues = operator.methodcaller("values") - - viewitems = operator.methodcaller("items") -else: - def iterkeys(d, **kw): - return d.iterkeys(**kw) - - def itervalues(d, **kw): - return d.itervalues(**kw) - - def iteritems(d, **kw): - return d.iteritems(**kw) - - def iterlists(d, **kw): - return d.iterlists(**kw) - - viewkeys = operator.methodcaller("viewkeys") - - viewvalues = operator.methodcaller("viewvalues") - - viewitems = operator.methodcaller("viewitems") - -_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.") -_add_doc(itervalues, "Return an iterator over the values of a dictionary.") -_add_doc(iteritems, - "Return an iterator over the (key, value) pairs of a dictionary.") -_add_doc(iterlists, - "Return an iterator over the (key, [values]) pairs of a dictionary.") - - -if PY3: - def b(s): - return s.encode("latin-1") - - def u(s): - return s - unichr = chr - import struct - int2byte = struct.Struct(">B").pack - del struct - byte2int = operator.itemgetter(0) - indexbytes = operator.getitem - iterbytes = iter - import io - StringIO = io.StringIO - BytesIO = io.BytesIO - del io - _assertCountEqual = "assertCountEqual" - if sys.version_info[1] <= 1: - _assertRaisesRegex = "assertRaisesRegexp" - _assertRegex = "assertRegexpMatches" - else: - _assertRaisesRegex = "assertRaisesRegex" - _assertRegex = "assertRegex" -else: - def b(s): - return s - # Workaround for standalone backslash - - def u(s): - return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape") - unichr = unichr - int2byte = chr - - def byte2int(bs): - return ord(bs[0]) - - def indexbytes(buf, i): - return ord(buf[i]) - iterbytes = functools.partial(itertools.imap, ord) - import StringIO - StringIO = BytesIO = StringIO.StringIO - _assertCountEqual = "assertItemsEqual" - _assertRaisesRegex = "assertRaisesRegexp" - _assertRegex = "assertRegexpMatches" -_add_doc(b, """Byte literal""") -_add_doc(u, """Text literal""") - - -def assertCountEqual(self, *args, **kwargs): - return getattr(self, _assertCountEqual)(*args, **kwargs) - - -def assertRaisesRegex(self, *args, **kwargs): - return getattr(self, _assertRaisesRegex)(*args, **kwargs) - - -def assertRegex(self, *args, **kwargs): - return getattr(self, _assertRegex)(*args, **kwargs) - - -if PY3: - exec_ = getattr(moves.builtins, "exec") - - def reraise(tp, value, tb=None): - try: - if value is None: - value = tp() - if value.__traceback__ is not tb: - raise value.with_traceback(tb) - raise value - finally: - value = None - tb = None - -else: - def exec_(_code_, _globs_=None, _locs_=None): - """Execute code in a namespace.""" - if _globs_ is None: - frame = sys._getframe(1) - _globs_ = frame.f_globals - if _locs_ is None: - _locs_ = frame.f_locals - del frame - elif _locs_ is None: - _locs_ = _globs_ - exec("""exec _code_ in _globs_, _locs_""") - - exec_("""def reraise(tp, value, tb=None): - try: - raise tp, value, tb - finally: - tb = None -""") - - -if sys.version_info[:2] == (3, 2): - exec_("""def raise_from(value, from_value): - try: - if from_value is None: - raise value - raise value from from_value - finally: - value = None -""") -elif sys.version_info[:2] > (3, 2): - exec_("""def raise_from(value, from_value): - try: - raise value from from_value - finally: - value = None -""") -else: - def raise_from(value, from_value): - raise value - - -print_ = getattr(moves.builtins, "print", None) -if print_ is None: - def print_(*args, **kwargs): - """The new-style print function for Python 2.4 and 2.5.""" - fp = kwargs.pop("file", sys.stdout) - if fp is None: - return - - def write(data): - if not isinstance(data, basestring): - data = str(data) - # If the file has an encoding, encode unicode with it. - if (isinstance(fp, file) and - isinstance(data, unicode) and - fp.encoding is not None): - errors = getattr(fp, "errors", None) - if errors is None: - errors = "strict" - data = data.encode(fp.encoding, errors) - fp.write(data) - want_unicode = False - sep = kwargs.pop("sep", None) - if sep is not None: - if isinstance(sep, unicode): - want_unicode = True - elif not isinstance(sep, str): - raise TypeError("sep must be None or a string") - end = kwargs.pop("end", None) - if end is not None: - if isinstance(end, unicode): - want_unicode = True - elif not isinstance(end, str): - raise TypeError("end must be None or a string") - if kwargs: - raise TypeError("invalid keyword arguments to print()") - if not want_unicode: - for arg in args: - if isinstance(arg, unicode): - want_unicode = True - break - if want_unicode: - newline = unicode("\n") - space = unicode(" ") - else: - newline = "\n" - space = " " - if sep is None: - sep = space - if end is None: - end = newline - for i, arg in enumerate(args): - if i: - write(sep) - write(arg) - write(end) -if sys.version_info[:2] < (3, 3): - _print = print_ - - def print_(*args, **kwargs): - fp = kwargs.get("file", sys.stdout) - flush = kwargs.pop("flush", False) - _print(*args, **kwargs) - if flush and fp is not None: - fp.flush() - -_add_doc(reraise, """Reraise an exception.""") - -if sys.version_info[0:2] < (3, 4): - def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS, - updated=functools.WRAPPER_UPDATES): - def wrapper(f): - f = functools.wraps(wrapped, assigned, updated)(f) - f.__wrapped__ = wrapped - return f - return wrapper -else: - wraps = functools.wraps - - -def with_metaclass(meta, *bases): - """Create a base class with a metaclass.""" - # This requires a bit of explanation: the basic idea is to make a dummy - # metaclass for one level of class instantiation that replaces itself with - # the actual metaclass. - class metaclass(type): - - def __new__(cls, name, this_bases, d): - if sys.version_info[:2] >= (3, 7): - # This version introduced PEP 560 that requires a bit - # of extra care (we mimic what is done by __build_class__). - resolved_bases = types.resolve_bases(bases) - if resolved_bases is not bases: - d['__orig_bases__'] = bases - else: - resolved_bases = bases - return meta(name, resolved_bases, d) - - @classmethod - def __prepare__(cls, name, this_bases): - return meta.__prepare__(name, bases) - return type.__new__(metaclass, 'temporary_class', (), {}) - - -def add_metaclass(metaclass): - """Class decorator for creating a class with a metaclass.""" - def wrapper(cls): - orig_vars = cls.__dict__.copy() - slots = orig_vars.get('__slots__') - if slots is not None: - if isinstance(slots, str): - slots = [slots] - for slots_var in slots: - orig_vars.pop(slots_var) - orig_vars.pop('__dict__', None) - orig_vars.pop('__weakref__', None) - if hasattr(cls, '__qualname__'): - orig_vars['__qualname__'] = cls.__qualname__ - return metaclass(cls.__name__, cls.__bases__, orig_vars) - return wrapper - - -def ensure_binary(s, encoding='utf-8', errors='strict'): - """Coerce **s** to six.binary_type. - - For Python 2: - - `unicode` -> encoded to `str` - - `str` -> `str` - - For Python 3: - - `str` -> encoded to `bytes` - - `bytes` -> `bytes` - """ - if isinstance(s, text_type): - return s.encode(encoding, errors) - elif isinstance(s, binary_type): - return s - else: - raise TypeError("not expecting type '%s'" % type(s)) - - -def ensure_str(s, encoding='utf-8', errors='strict'): - """Coerce *s* to `str`. - - For Python 2: - - `unicode` -> encoded to `str` - - `str` -> `str` - - For Python 3: - - `str` -> `str` - - `bytes` -> decoded to `str` - """ - if not isinstance(s, (text_type, binary_type)): - raise TypeError("not expecting type '%s'" % type(s)) - if PY2 and isinstance(s, text_type): - s = s.encode(encoding, errors) - elif PY3 and isinstance(s, binary_type): - s = s.decode(encoding, errors) - return s - - -def ensure_text(s, encoding='utf-8', errors='strict'): - """Coerce *s* to six.text_type. - - For Python 2: - - `unicode` -> `unicode` - - `str` -> `unicode` - - For Python 3: - - `str` -> `str` - - `bytes` -> decoded to `str` - """ - if isinstance(s, binary_type): - return s.decode(encoding, errors) - elif isinstance(s, text_type): - return s - else: - raise TypeError("not expecting type '%s'" % type(s)) - - - -def python_2_unicode_compatible(klass): - """ - A decorator that defines __unicode__ and __str__ methods under Python 2. - Under Python 3 it does nothing. - - To support Python 2 and 3 with a single code base, define a __str__ method - returning text and apply this decorator to the class. - """ - if PY2: - if '__str__' not in klass.__dict__: - raise ValueError("@python_2_unicode_compatible cannot be applied " - "to %s because it doesn't define __str__()." % - klass.__name__) - klass.__unicode__ = klass.__str__ - klass.__str__ = lambda self: self.__unicode__().encode('utf-8') - return klass - - -# Complete the moves implementation. -# This code is at the end of this module to speed up module loading. -# Turn this module into a package. -__path__ = [] # required for PEP 302 and PEP 451 -__package__ = __name__ # see PEP 366 @ReservedAssignment -if globals().get("__spec__") is not None: - __spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable -# Remove other six meta path importers, since they cause problems. This can -# happen if six is removed from sys.modules and then reloaded. (Setuptools does -# this for some reason.) -if sys.meta_path: - for i, importer in enumerate(sys.meta_path): - # Here's some real nastiness: Another "instance" of the six module might - # be floating around. Therefore, we can't use isinstance() to check for - # the six meta path importer, since the other six instance will have - # inserted an importer with different class. - if (type(importer).__name__ == "_SixMetaPathImporter" and - importer.name == __name__): - del sys.meta_path[i] - break - del i, importer -# Finally, add the importer to the meta path import hook. -sys.meta_path.append(_importer) diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index 16c073d01..54179cdfd 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -55,9 +55,6 @@ # to be exposed as part of the API. from xmlrpc.client import Error, ProtocolError, ResponseError # noqa -# Python 2/3 compatibility -from .lib import six -from .lib import sgutils from .lib.httplib2 import Http, ProxyInfo, socks from .lib.sgtimezone import SgTimezone @@ -734,7 +731,7 @@ def _split_url(self, base_url): In python 3.8 `urllib.parse.splituser` was deprecated warning devs to use `urllib.parse.urlparse`. """ - if six.PY38: + if (sys.version_info.major, sys.version_info.minor) >= (3, 8): auth = None results = urllib.parse.urlparse(base_url) server = results.hostname @@ -4603,7 +4600,7 @@ def connect(self): "Connect to a host on a given (SSL) port." super().connect(self) # Now that the regular HTTP socket has been created, wrap it with our SSL certs. - if six.PY38: + if (sys.version_info.major, sys.version_info.minor) >= (3, 8): context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) context.verify_mode = ssl.CERT_REQUIRED context.check_hostname = False diff --git a/tests/base.py b/tests/base.py index 3795d93af..b20b5f1ef 100644 --- a/tests/base.py +++ b/tests/base.py @@ -13,7 +13,6 @@ import shotgun_api3 as api from shotgun_api3.shotgun import ServerCapabilities -from shotgun_api3.lib import six THUMBNAIL_MAX_ATTEMPTS = 30 diff --git a/tests/test_api_long.py b/tests/test_api_long.py index 29a34e991..4b652fc4a 100644 --- a/tests/test_api_long.py +++ b/tests/test_api_long.py @@ -16,7 +16,6 @@ from . import base import random import shotgun_api3 -from shotgun_api3.lib import six class TestShotgunApiLong(base.LiveTestBase): diff --git a/tests/test_client.py b/tests/test_client.py index 65678cc18..6c254264b 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -26,8 +26,6 @@ import urllib.parse import urllib.error -from shotgun_api3.lib import six, sgutils - import shotgun_api3.lib.httplib2 as httplib2 import shotgun_api3 as api from shotgun_api3.shotgun import ServerCapabilities, SG_TIMEZONE From 0a03a6b460fcf34867ecac06076a61f823c8b698 Mon Sep 17 00:00:00 2001 From: Julien Langlois <16244608+julien-lang@users.noreply.github.com> Date: Wed, 10 Sep 2025 15:50:58 -0700 Subject: [PATCH 106/125] SG-38306 Remove Python 2 - Part 10 - SSL (#372) * SSL cleanups --------- Co-authored-by: Eduardo Chauca --- docs/advanced/iron_python.rst | 12 ++-- docs/reference.rst | 2 +- shotgun_api3/shotgun.py | 111 ++++------------------------------ tests/test_api.py | 71 ---------------------- tests/test_client.py | 9 +-- 5 files changed, 21 insertions(+), 184 deletions(-) diff --git a/docs/advanced/iron_python.rst b/docs/advanced/iron_python.rst index 62ad6d791..06786ab96 100644 --- a/docs/advanced/iron_python.rst +++ b/docs/advanced/iron_python.rst @@ -7,6 +7,12 @@ that we will be compatible with future releases of IronPython. While we don't of IronPython, we certainly will do our best to figure out any issues that come up while using it and how to avoid them. + +Legacy Information +------------------ + +This following information is provided for historical purposes only. + As of July 9, 2015 you can look at this fork of the repo to see what changes were needed as of that date to make things work. The original fork was as of v3.0.20 of the API. Big thanks to our awesome clients Pixomondo for making their work public and letting us refer to it: @@ -20,12 +26,6 @@ v3.0.20 can be used with IronPython with a little bit of added work: https://bitbucket.org/jdhardy/ironpythonzlib/src/. And the blog post about it here http://blog.jdhardy.ca/2008/12/solving-zlib-problem-ironpythonzlib.html -- If you encounter any SSL errors like - ``unknown field: SERIALNUMBER=0123456789`` or ``:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed``. - For now you can workaround this problem by disabling ssl certificate validation which we've - encountered some intermittent issues with. Set ``NO_SSL_VALIDATION = True`` for either case. - See :const:`shotgun_api3.shotgun.NO_SSL_VALIDATION` - - If you encounter ``LookupError: unknown encoding: idna``, you can force utf-8 by changing iri2uri.py ~ln 71 from ``authority = authority.encode('idna')`` to ``authority = authority.encode('utf-8')`` diff --git a/docs/reference.rst b/docs/reference.rst index 96c917469..6b16a37bd 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -15,7 +15,7 @@ The :mod:`~shotgun_api3.shotgun` module is a container for the :class:`~shotgun. class. There are a couple of useful attributes to note. .. automodule:: shotgun_api3.shotgun - :members: NO_SSL_VALIDATION, LOG + :members: LOG :private-members: :special-members: diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index 54179cdfd..128896cc7 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -78,14 +78,6 @@ SHOTGUN_API_DISABLE_ENTITY_OPTIMIZATION = False -NO_SSL_VALIDATION = False -""" -Turns off hostname matching validation for SSL certificates - -Sometimes there are cases where certificate validation should be disabled. For example, if you -have a self-signed internal certificate that isn't included in our certificate bundle, you may -not require the added security provided by enforcing this. -""" # ---------------------------------------------------------------------------- # Version @@ -327,9 +319,7 @@ class ClientCapabilities(object): :ivar str local_path_field: The PTR field used for local file paths. This is calculated using the value of ``platform``. Ex. ``local_path_mac``. :ivar str py_version: Simple version of Python executable as a string. Eg. ``3.9``. - :ivar str ssl_version: Version of OpenSSL installed. Eg. ``OpenSSL 1.0.2g 1 Mar 2016``. This - info is only available in Python 2.7+ if the ssl module was imported successfully. - Defaults to ``unknown`` + :ivar str ssl_version: Version of OpenSSL installed. Eg. ``OpenSSL 1.0.2g 1 Mar 2016``. """ def __init__(self): @@ -350,14 +340,7 @@ def __init__(self): self.local_path_field = None self.py_version = ".".join(str(x) for x in sys.version_info[:2]) - - # extract the OpenSSL version if we can. The version is only available in Python 2.7 and - # only if we successfully imported ssl - self.ssl_version = "unknown" - try: - self.ssl_version = ssl.OPENSSL_VERSION - except (AttributeError, NameError): - pass + self.ssl_version = ssl.OPENSSL_VERSION def __str__(self): return ( @@ -425,7 +408,6 @@ def __init__(self, sg): self.proxy_pass = None self.session_token = None self.authorization = None - self.no_ssl_validation = False self.localized = False def set_server_params(self, base_url): @@ -616,7 +598,6 @@ def __init__( self.config.session_token = session_token self.config.sudo_as_login = sudo_as_login self.config.convert_datetimes_to_utc = convert_datetimes_to_utc - self.config.no_ssl_validation = NO_SSL_VALIDATION self.config.raw_http_proxy = http_proxy try: @@ -2264,14 +2245,10 @@ def reset_user_agent(self): ua_platform = self.client_caps.platform.capitalize() # create ssl validation string based on settings - validation_str = "validate" - if self.config.no_ssl_validation: - validation_str = "no-validate" - self._user_agents = [ "shotgun-json (%s)" % __version__, "Python %s (%s)" % (self.client_caps.py_version, ua_platform), - "ssl %s (%s)" % (self.client_caps.ssl_version, validation_str), + "ssl %s" % (self.client_caps.ssl_version), ] def set_session_uuid(self, session_uuid): @@ -3543,8 +3520,14 @@ def _build_opener(self, handler): Build urllib2 opener with appropriate proxy handler. """ handlers = [] - if self.__ca_certs and not NO_SSL_VALIDATION: - handlers.append(CACertsHTTPSHandler(self.__ca_certs)) + if self.__ca_certs: + handlers.append( + urllib.request.HTTPSHandler( + context=ssl.create_default_context( + cafile=self.__ca_certs, + ), + ), + ) if self.config.proxy_handler: handlers.append(self.config.proxy_handler) @@ -3613,23 +3596,6 @@ def _get_certs_file(cls, ca_certs): cert_file = os.path.join(cur_dir, "lib", "certifi", "cacert.pem") return cert_file - def _turn_off_ssl_validation(self): - """ - Turn off SSL certificate validation. - """ - global NO_SSL_VALIDATION - self.config.no_ssl_validation = True - NO_SSL_VALIDATION = True - # reset ssl-validation in user-agents - self._user_agents = [ - ( - "ssl %s (no-validate)" % self.client_caps.ssl_version - if ua.startswith("ssl ") - else ua - ) - for ua in self._user_agents - ] - # Deprecated methods from old wrapper def schema(self, entity_type): """ @@ -3843,44 +3809,7 @@ def _make_call(self, verb, path, body, headers): if attempt == max_rpc_attempts: LOG.debug("Request failed. Giving up after %d attempts." % attempt) raise - # This is the exact same block as the "except Exception" bellow. - # We need to do it here because the next except will match it - # otherwise and will not re-attempt. - # When we drop support of Python 2 and we will probably drop the - # next except, we might want to remove this except too. except (ssl.SSLError, ssl.CertificateError) as e: - # Test whether the exception is due to the fact that this is an older version of - # Python that cannot validate certificates encrypted with SHA-2. If it is, then - # fall back on disabling the certificate validation and try again - unless the - # SHOTGUN_FORCE_CERTIFICATE_VALIDATION environment variable has been set by the - # user. In that case we simply raise the exception. Any other exceptions simply - # get raised as well. - # - # For more info see: - # https://www.shotgridsoftware.com/blog/important-ssl-certificate-renewal-and-sha-2/ - # - # SHA-2 errors look like this: - # [Errno 1] _ssl.c:480: error:0D0C50A1:asn1 encoding routines:ASN1_item_verify: - # unknown message digest algorithm - # - # Any other exceptions simply get raised. - if ( - "unknown message digest algorithm" not in str(e) - or "SHOTGUN_FORCE_CERTIFICATE_VALIDATION" in os.environ - ): - raise - - if self.config.no_ssl_validation is False: - LOG.warning( - "SSL Error: this Python installation is incompatible with " - "certificates signed with SHA-2. Disabling certificate validation. " - "For more information, see https://www.shotgridsoftware.com/blog/" - "important-ssl-certificate-renewal-and-sha-2/" - ) - self._turn_off_ssl_validation() - # reload user agent to reflect that we have turned off ssl validation - req_headers["user-agent"] = "; ".join(self._user_agents) - self._close_connection() if attempt == max_rpc_attempts: LOG.debug("Request failed. Giving up after %d attempts." % attempt) @@ -4142,14 +4071,12 @@ def _get_connection(self): timeout=self.config.timeout_secs, ca_certs=self.__ca_certs, proxy_info=pi, - disable_ssl_certificate_validation=self.config.no_ssl_validation, ) else: self._connection = Http( timeout=self.config.timeout_secs, ca_certs=self.__ca_certs, proxy_info=None, - disable_ssl_certificate_validation=self.config.no_ssl_validation, ) return self._connection @@ -4613,22 +4540,6 @@ def connect(self): ) -class CACertsHTTPSHandler(urllib.request.HTTPHandler): - """ - Handler that ensures https connections are created with the custom CA certs. - """ - - def __init__(self, cacerts): - super().__init__(self) - self.__ca_certs = cacerts - - def https_open(self, req): - return self.do_open(self.create_https_connection, req) - - def create_https_connection(self, *args, **kwargs): - return CACertsHTTPSConnection(*args, ca_certs=self.__ca_certs, **kwargs) - - # Helpers from the previous API, left as is. # Based on http://code.activestate.com/recipes/146306/ class FormPostHandler(urllib.request.BaseHandler): diff --git a/tests/test_api.py b/tests/test_api.py index cbbc115fe..19b738f60 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -2214,77 +2214,6 @@ def my_side_effect2(*args, **kwargs): finally: self.sg.config.rpc_attempt_interval = bak_rpc_attempt_interval - @unittest.mock.patch("shotgun_api3.shotgun.Http.request") - def test_sha2_error(self, mock_request): - # Simulate the exception raised with SHA-2 errors - mock_request.side_effect = ssl.SSLError( - "[Errno 1] _ssl.c:480: error:0D0C50A1:asn1 " - "encoding routines:ASN1_item_verify: unknown message digest " - "algorithm" - ) - - # save the original state - original_env_val = os.environ.pop("SHOTGUN_FORCE_CERTIFICATE_VALIDATION", None) - - # ensure we're starting with the right values - self.sg.reset_user_agent() - - # ensure the initial settings are correct. These will be different depending on whether - # the ssl module imported successfully or not. - if "ssl" in sys.modules: - self.assertFalse(self.sg.config.no_ssl_validation) - self.assertFalse(shotgun_api3.shotgun.NO_SSL_VALIDATION) - self.assertTrue("(validate)" in " ".join(self.sg._user_agents)) - self.assertFalse("(no-validate)" in " ".join(self.sg._user_agents)) - else: - self.assertTrue(self.sg.config.no_ssl_validation) - self.assertTrue(shotgun_api3.shotgun.NO_SSL_VALIDATION) - self.assertFalse("(validate)" in " ".join(self.sg._user_agents)) - self.assertTrue("(no-validate)" in " ".join(self.sg._user_agents)) - - try: - self.sg.info() - except ssl.SSLError: - # ensure the api has reset the values in the correct fallback behavior - self.assertTrue(self.sg.config.no_ssl_validation) - self.assertTrue(shotgun_api3.shotgun.NO_SSL_VALIDATION) - self.assertFalse("(validate)" in " ".join(self.sg._user_agents)) - self.assertTrue("(no-validate)" in " ".join(self.sg._user_agents)) - - if original_env_val is not None: - os.environ["SHOTGUN_FORCE_CERTIFICATE_VALIDATION"] = original_env_val - - @unittest.mock.patch("shotgun_api3.shotgun.Http.request") - def test_sha2_error_with_strict(self, mock_request): - # Simulate the exception raised with SHA-2 errors - mock_request.side_effect = ssl.SSLError( - "[Errno 1] _ssl.c:480: error:0D0C50A1:asn1 " - "encoding routines:ASN1_item_verify: unknown message digest " - "algorithm" - ) - - # save the original state - original_env_val = os.environ.pop("SHOTGUN_FORCE_CERTIFICATE_VALIDATION", None) - os.environ["SHOTGUN_FORCE_CERTIFICATE_VALIDATION"] = "1" - - # ensure we're starting with the right values - self.sg.config.no_ssl_validation = False - shotgun_api3.shotgun.NO_SSL_VALIDATION = False - self.sg.reset_user_agent() - - try: - self.sg.info() - except ssl.SSLError: - # ensure the api has NOT reset the values in the fallback behavior because we have - # set the env variable to force validation - self.assertFalse(self.sg.config.no_ssl_validation) - self.assertFalse(shotgun_api3.shotgun.NO_SSL_VALIDATION) - self.assertFalse("(no-validate)" in " ".join(self.sg._user_agents)) - self.assertTrue("(validate)" in " ".join(self.sg._user_agents)) - - if original_env_val is not None: - os.environ["SHOTGUN_FORCE_CERTIFICATE_VALIDATION"] = original_env_val - @unittest.mock.patch.object(urllib.request.OpenerDirector, "open") def test_sanitized_auth_params(self, mock_open): # Simulate the server blowing up and giving us a 500 error diff --git a/tests/test_client.py b/tests/test_client.py index 6c254264b..1e41ea914 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -269,12 +269,11 @@ def test_user_agent(self): args, _ = self.sg._http_request.call_args (_, _, _, headers) = args ssl_validate_lut = {True: "no-validate", False: "validate"} - expected = "shotgun-json (%s); Python %s (%s); ssl %s (%s)" % ( + expected = "shotgun-json (%s); Python %s (%s); ssl %s" % ( api.__version__, client_caps.py_version, client_caps.platform.capitalize(), client_caps.ssl_version, - ssl_validate_lut[config.no_ssl_validation], ) self.assertEqual(expected, headers.get("user-agent")) @@ -283,12 +282,11 @@ def test_user_agent(self): self.sg.info() args, _ = self.sg._http_request.call_args (_, _, _, headers) = args - expected = "shotgun-json (%s); Python %s (%s); ssl %s (%s); test-agent" % ( + expected = "shotgun-json (%s); Python %s (%s); ssl %s; test-agent" % ( api.__version__, client_caps.py_version, client_caps.platform.capitalize(), client_caps.ssl_version, - ssl_validate_lut[config.no_ssl_validation], ) self.assertEqual(expected, headers.get("user-agent")) @@ -297,12 +295,11 @@ def test_user_agent(self): self.sg.info() args, _ = self.sg._http_request.call_args (_, _, _, headers) = args - expected = "shotgun-json (%s); Python %s (%s); ssl %s (%s)" % ( + expected = "shotgun-json (%s); Python %s (%s); ssl %s" % ( api.__version__, client_caps.py_version, client_caps.platform.capitalize(), client_caps.ssl_version, - ssl_validate_lut[config.no_ssl_validation], ) self.assertEqual(expected, headers.get("user-agent")) From 09dd4cbd395fc1c8a67bdb99bf14111b296a1ab9 Mon Sep 17 00:00:00 2001 From: Eduardo Chauca <166560435+eduardoChaucaGallegos@users.noreply.github.com> Date: Wed, 10 Sep 2025 18:34:01 -0500 Subject: [PATCH 107/125] Packaging for the v3.9.0 release (#413) * packaging for the v3.9.0 release --- HISTORY.rst | 8 +++++++- setup.py | 2 +- shotgun_api3/shotgun.py | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index c19b61fec..30b68b792 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -4,7 +4,13 @@ Flow Production Tracking Python API Changelog Here you can see the full list of changes between each Python API release. -v3.8.5 (2025 Xxx X) +v3.9.0 (2025 Sep 10) +=================== + +- Removed Python 2 code. +- Removed the six module. Note: if your code depends on the six library previously included in this package, you will need to update it, as it is no longer supported. + +v3.8.5 (2025 Jul 31) =================== - We don't want to retry on general exceptions (e.g. timeout or remote disconnection) diff --git a/setup.py b/setup.py index 9240486b5..647661096 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ setup( name="shotgun_api3", - version="3.8.4", + version="3.9.0", description="Flow Production Tracking Python API", long_description=readme, author="Autodesk", diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index 128896cc7..67996c9f3 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -81,7 +81,7 @@ # ---------------------------------------------------------------------------- # Version -__version__ = "3.8.4" +__version__ = "3.9.0" # ---------------------------------------------------------------------------- # Errors From c1d99f5d0533fa732f78aeca1af66f9550b68fb9 Mon Sep 17 00:00:00 2001 From: Julien Langlois <16244608+julien-lang@users.noreply.github.com> Date: Wed, 24 Sep 2025 11:50:02 -0700 Subject: [PATCH 108/125] Fixup "Title underline too short" warning messages (#414) Those happened when building the documentation --- HISTORY.rst | 4 ++-- docs/cookbook/attachments.rst | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 30b68b792..7b9fc4018 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -5,13 +5,13 @@ Flow Production Tracking Python API Changelog Here you can see the full list of changes between each Python API release. v3.9.0 (2025 Sep 10) -=================== +==================== - Removed Python 2 code. - Removed the six module. Note: if your code depends on the six library previously included in this package, you will need to update it, as it is no longer supported. v3.8.5 (2025 Jul 31) -=================== +==================== - We don't want to retry on general exceptions (e.g. timeout or remote disconnection) because we might send a resource modification request (create, batch create, etc) and diff --git a/docs/cookbook/attachments.rst b/docs/cookbook/attachments.rst index 934ed58e8..de992431d 100644 --- a/docs/cookbook/attachments.rst +++ b/docs/cookbook/attachments.rst @@ -304,7 +304,7 @@ defaults. Any other keys that are provided will be ignored. Alternative to ``local_path`` Example 1: Using ``local_path`` ------------------------------- +------------------------------- :: @@ -345,7 +345,7 @@ the most appropriate specific LocalStorage match and assigned it to local_storag Example 2: Using ``relative_path`` ---------------------------------- +---------------------------------- :: From 5112d082b6fa9e017529898617ff2ff3d0a46e48 Mon Sep 17 00:00:00 2001 From: Julien Langlois <16244608+julien-lang@users.noreply.github.com> Date: Thu, 2 Oct 2025 15:34:07 -0700 Subject: [PATCH 109/125] SG-40165 Update support URL (#411) --- docs/advanced.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/advanced.rst b/docs/advanced.rst index f10437ff5..b4bc73a35 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -5,7 +5,7 @@ Advanced Topics ############### Below are some more advanced topics regarding usage of the Python API. If you would like to see -something that's missing here, please feel free to contact support at https://knowledge.autodesk.com/contact-support +something that's missing here, please feel free to contact support at https://knowledge.autodesk.com/support with your suggestions and we'll get it added! .. toctree:: From c7c3bc7b82fd773d0ccbae87d6bfe891471a8fa6 Mon Sep 17 00:00:00 2001 From: Julien Langlois <16244608+julien-lang@users.noreply.github.com> Date: Mon, 6 Oct 2025 18:05:02 -0700 Subject: [PATCH 110/125] SG-40165 Update Autodesk support URL (#416) --- README.md | 2 +- docs/advanced.rst | 2 +- docs/reference.rst | 2 +- setup.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f37c5dc21..000e37d84 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ # Flow Production Tracking Python API -Autodesk provides a simple Python-based API for accessing Flow Production Tracking and integrating with other tools. This is the official API that is maintained by Autodesk (https://knowledge.autodesk.com/contact-support) +Autodesk provides a simple Python-based API for accessing Flow Production Tracking and integrating with other tools. This is the official API that is maintained by Autodesk (https://www.autodesk.com/support) The latest version can always be found at http://github.com/shotgunsoftware/python-api diff --git a/docs/advanced.rst b/docs/advanced.rst index b4bc73a35..762f851ea 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -5,7 +5,7 @@ Advanced Topics ############### Below are some more advanced topics regarding usage of the Python API. If you would like to see -something that's missing here, please feel free to contact support at https://knowledge.autodesk.com/support +something that's missing here, please feel free to contact support at https://www.autodesk.com/support with your suggestions and we'll get it added! .. toctree:: diff --git a/docs/reference.rst b/docs/reference.rst index 6b16a37bd..28bfabf1b 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -931,7 +931,7 @@ usage. This **does not** mean your Flow Production Tracking server performance will suffer in general, just any pages that are specifically displaying EventLogEntries in the web application, or API queries on the event log that are run. We are always looking for ways to improve this in the future. If you have any -immediate concerns, please `reach out to our support team `_ +immediate concerns, please `reach out to our support team `_ ********************* Environment Variables diff --git a/setup.py b/setup.py index 647661096..8d903f5f1 100644 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ description="Flow Production Tracking Python API", long_description=readme, author="Autodesk", - author_email="https://www.autodesk.com/support/contact-support", + author_email="https://www.autodesk.com/support", url="https://github.com/shotgunsoftware/python-api", license=license, packages=find_packages(exclude=("tests",)), From 36724cf42cd27eedd727586a7f1cd9106236fbce Mon Sep 17 00:00:00 2001 From: Julien Langlois <16244608+julien-lang@users.noreply.github.com> Date: Thu, 16 Oct 2025 13:22:59 -0700 Subject: [PATCH 111/125] SG-40026 Renove unused CACertsHTTPSConnection class (#418) * Renove unused CACertsHTTPSConnection class Should have been removed in #372 * Update shotgun_api3/shotgun.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- shotgun_api3/shotgun.py | 39 ++------------------------------------- tests/test_client.py | 4 ++-- 2 files changed, 4 insertions(+), 39 deletions(-) diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index 67996c9f3..6400ff373 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -4382,7 +4382,7 @@ def _upload_data_to_storage(self, data, content_type, size, storage_url): else: break else: - raise ShotgunError("Max attemps limit reached.") + raise ShotgunError("Max attempts limit reached.") etag = result.info()["Etag"] LOG.debug("Part upload completed successfully.") @@ -4502,42 +4502,7 @@ def _send_form(self, url, params): return result else: - raise ShotgunError("Max attemps limit reached.") - - -class CACertsHTTPSConnection(http.client.HTTPConnection): - """ " - This class allows to create an HTTPS connection that uses the custom certificates - passed in. - """ - - default_port = http.client.HTTPS_PORT - - def __init__(self, *args, **kwargs): - """ - :param args: Positional arguments passed down to the base class. - :param ca_certs: Path to the custom CA certs file. - :param kwargs: Keyword arguments passed down to the bas class - """ - # Pop that argument, - self.__ca_certs = kwargs.pop("ca_certs") - super().__init__(self, *args, **kwargs) - - def connect(self): - "Connect to a host on a given (SSL) port." - super().connect(self) - # Now that the regular HTTP socket has been created, wrap it with our SSL certs. - if (sys.version_info.major, sys.version_info.minor) >= (3, 8): - context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) - context.verify_mode = ssl.CERT_REQUIRED - context.check_hostname = False - if self.__ca_certs: - context.load_verify_locations(self.__ca_certs) - self.sock = context.wrap_socket(self.sock) - else: - self.sock = ssl.wrap_socket( - self.sock, ca_certs=self.__ca_certs, cert_reqs=ssl.CERT_REQUIRED - ) + raise ShotgunError("Max attempts limit reached.") # Helpers from the previous API, left as is. diff --git a/tests/test_client.py b/tests/test_client.py index 1e41ea914..a43e3ed4a 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -523,7 +523,7 @@ def test_upload_s3_urlerror__get_attachment_upload_info(self): # Test the exception message the_exception = cm.exception - self.assertEqual(str(the_exception), "Max attemps limit reached.") + self.assertEqual(str(the_exception), "Max attempts limit reached.") def test_upload_s3_urlerror__upload_to_storage(self): """ @@ -557,7 +557,7 @@ def test_upload_s3_urlerror__upload_to_storage(self): # Test the exception message the_exception = cm.exception - self.assertEqual(str(the_exception), "Max attemps limit reached.") + self.assertEqual(str(the_exception), "Max attempts limit reached.") def test_transform_data(self): """Outbound data is transformed""" From 304a1534ac4d7758ba97d354f27a734fbd625622 Mon Sep 17 00:00:00 2001 From: Julien Langlois <16244608+julien-lang@users.noreply.github.com> Date: Mon, 27 Oct 2025 11:11:53 -0700 Subject: [PATCH 112/125] SG-39039 Issue a deprecation warning if using Python version <3.9 (#417) --- shotgun_api3/__init__.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/shotgun_api3/__init__.py b/shotgun_api3/__init__.py index d296aa97a..943a9fa8b 100644 --- a/shotgun_api3/__init__.py +++ b/shotgun_api3/__init__.py @@ -8,6 +8,19 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. +import sys +import warnings + +if sys.version_info < (3, 9): + warnings.warn( + "Python versions older than 3.9 are no longer supported since 2025-03 " + "and compatibility will be removed at any time after 2026-01. " + "Please update to Python 3.9 or a newer supported version.", + DeprecationWarning, + stacklevel=2, + ) + + from .shotgun import ( Shotgun, ShotgunError, From 03811c10914710b60934c0095444abbaafa70a91 Mon Sep 17 00:00:00 2001 From: Carlos Villavicencio Date: Thu, 6 Nov 2025 14:34:17 -0500 Subject: [PATCH 113/125] SG-39414 Add type annotations (#422) * Add type annotations (from #393) * Add more fixes * Remove unused types --- setup.py | 6 +- shotgun_api3/lib/mockgun/mockgun.py | 5 +- shotgun_api3/lib/mockgun/schema.py | 2 +- shotgun_api3/py.typed | 0 shotgun_api3/shotgun.py | 585 +++++++++++++++++----------- 5 files changed, 371 insertions(+), 227 deletions(-) create mode 100644 shotgun_api3/py.typed diff --git a/setup.py b/setup.py index 8d903f5f1..0ddda9d79 100644 --- a/setup.py +++ b/setup.py @@ -30,17 +30,17 @@ packages=find_packages(exclude=("tests",)), script_args=sys.argv[1:], include_package_data=True, - package_data={"": ["cacerts.txt", "cacert.pem"]}, + package_data={"": ["cacerts.txt", "cacert.pem", "py.typed"]}, zip_safe=False, - python_requires=">=3.7.0", + python_requires=">=3.9.0", classifiers=[ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Programming Language :: Python", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Operating System :: OS Independent", ], ) diff --git a/shotgun_api3/lib/mockgun/mockgun.py b/shotgun_api3/lib/mockgun/mockgun.py index 45d0b2aa5..522e162d9 100644 --- a/shotgun_api3/lib/mockgun/mockgun.py +++ b/shotgun_api3/lib/mockgun/mockgun.py @@ -115,6 +115,7 @@ """ import datetime +from typing import Any from ... import ShotgunError from ...shotgun import _Config @@ -580,7 +581,7 @@ def _get_new_row(self, entity_type): row[field] = default_value return row - def _compare(self, field_type, lval, operator, rval): + def _compare(self, field_type: str, lval: Any, operator: str, rval: Any) -> bool: """ Compares a field using the operator and value provide by the filter. @@ -797,7 +798,7 @@ def _row_matches_filter(self, entity_type, row, sg_filter, retired_only): return self._compare(field_type, lval, operator, rval) - def _rearrange_filters(self, filters): + def _rearrange_filters(self, filters: list) -> None: """ Modifies the filter syntax to turn it into a list of three items regardless of the actual filter. Most of the filters are list of three elements, so this doesn't change much. diff --git a/shotgun_api3/lib/mockgun/schema.py b/shotgun_api3/lib/mockgun/schema.py index ab671629d..f5d9312cc 100644 --- a/shotgun_api3/lib/mockgun/schema.py +++ b/shotgun_api3/lib/mockgun/schema.py @@ -47,7 +47,7 @@ class SchemaFactory(object): _schema_cache_path = None @classmethod - def get_schemas(cls, schema_path, schema_entity_path): + def get_schemas(cls, schema_path: str, schema_entity_path: str) -> tuple: """ Retrieves the schemas from disk. diff --git a/shotgun_api3/py.typed b/shotgun_api3/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index 6400ff373..0c0c9cd5c 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -50,6 +50,16 @@ import urllib.request import uuid # used for attachment upload import xml.etree.ElementTree +from typing import ( + Any, + BinaryIO, + Iterable, + Literal, + Optional, + TypedDict, + TypeVar, + Union, +) # Import Error and ResponseError (even though they're unused in this file) since they need # to be exposed as part of the API. @@ -83,6 +93,30 @@ # Version __version__ = "3.9.0" + +# ---------------------------------------------------------------------------- +# Types + + +T = TypeVar("T") + + +class OrderItem(TypedDict): + field_name: str + direction: str + + +class GroupingItem(TypedDict): + field: str + type: str + direction: str + + +class BaseEntity(TypedDict, total=False): + id: int + type: str + + # ---------------------------------------------------------------------------- # Errors @@ -168,7 +202,7 @@ class ServerCapabilities(object): the future. Therefore, usage of this class is discouraged. """ - def __init__(self, host, meta): + def __init__(self, host: str, meta: dict[str, Any]) -> None: """ ServerCapabilities.__init__ @@ -208,14 +242,14 @@ def __init__(self, host, meta): self.version = tuple(self.version[:3]) self._ensure_json_supported() - def _ensure_python_version_supported(self): + def _ensure_python_version_supported(self) -> None: """ Checks the if current Python version is supported. """ if sys.version_info < (3, 7): raise ShotgunError("This module requires Python version 3.7 or higher.") - def _ensure_support(self, feature, raise_hell=True): + def _ensure_support(self, feature: dict[str, Any], raise_hell: bool = True) -> bool: """ Checks the server version supports a given feature, raises an exception if it does not. @@ -243,13 +277,13 @@ def _ensure_support(self, feature, raise_hell=True): else: return True - def _ensure_json_supported(self): + def _ensure_json_supported(self) -> None: """ Ensures server has support for JSON API endpoint added in v2.4.0. """ self._ensure_support({"version": (2, 4, 0), "label": "JSON API"}) - def ensure_include_archived_projects(self): + def ensure_include_archived_projects(self) -> None: """ Ensures server has support for archived Projects feature added in v5.3.14. """ @@ -257,7 +291,7 @@ def ensure_include_archived_projects(self): {"version": (5, 3, 14), "label": "include_archived_projects parameter"} ) - def ensure_per_project_customization(self): + def ensure_per_project_customization(self) -> bool: """ Ensures server has support for per-project customization feature added in v5.4.4. """ @@ -265,7 +299,7 @@ def ensure_per_project_customization(self): {"version": (5, 4, 4), "label": "project parameter"}, True ) - def ensure_support_for_additional_filter_presets(self): + def ensure_support_for_additional_filter_presets(self) -> bool: """ Ensures server has support for additional filter presets feature added in v7.0.0. """ @@ -273,7 +307,7 @@ def ensure_support_for_additional_filter_presets(self): {"version": (7, 0, 0), "label": "additional_filter_presets parameter"}, True ) - def ensure_user_following_support(self): + def ensure_user_following_support(self) -> bool: """ Ensures server has support for listing items a user is following, added in v7.0.12. """ @@ -281,7 +315,7 @@ def ensure_user_following_support(self): {"version": (7, 0, 12), "label": "user_following parameter"}, True ) - def ensure_paging_info_without_counts_support(self): + def ensure_paging_info_without_counts_support(self) -> bool: """ Ensures server has support for optimized pagination, added in v7.4.0. """ @@ -289,7 +323,7 @@ def ensure_paging_info_without_counts_support(self): {"version": (7, 4, 0), "label": "optimized pagination"}, False ) - def ensure_return_image_urls_support(self): + def ensure_return_image_urls_support(self) -> bool: """ Ensures server has support for returning thumbnail URLs without additional round-trips, added in v3.3.0. """ @@ -297,7 +331,7 @@ def ensure_return_image_urls_support(self): {"version": (3, 3, 0), "label": "return thumbnail URLs"}, False ) - def __str__(self): + def __str__(self) -> str: return "ServerCapabilities: host %s, version %s, is_dev %s" % ( self.host, self.version, @@ -355,7 +389,7 @@ class _Config(object): Container for the client configuration. """ - def __init__(self, sg): + def __init__(self, sg: "Shotgun"): """ :param sg: Shotgun connection. """ @@ -376,41 +410,41 @@ def __init__(self, sg): # If the optional timeout parameter is given, blocking operations # (like connection attempts) will timeout after that many seconds # (if it is not given, the global default timeout setting is used) - self.timeout_secs = None + self.timeout_secs: Optional[float] = None self.api_ver = "api3" self.convert_datetimes_to_utc = True - self._records_per_page = None - self.api_key = None - self.script_name = None - self.user_login = None - self.user_password = None - self.auth_token = None - self.sudo_as_login = None + self._records_per_page: Optional[int] = None + self.api_key: Optional[str] = None + self.script_name: Optional[str] = None + self.user_login: Optional[str] = None + self.user_password: Optional[str] = None + self.auth_token: Optional[str] = None + self.sudo_as_login: Optional[str] = None # Authentication parameters to be folded into final auth_params dict - self.extra_auth_params = None + self.extra_auth_params: Optional[dict[str, Any]] = None # uuid as a string - self.session_uuid = None - self.scheme = None - self.server = None - self.api_path = None + self.session_uuid: Optional[str] = None + self.scheme: Optional[str] = None + self.server: Optional[str] = None + self.api_path: Optional[str] = None # The raw_http_proxy reflects the exact string passed in # to the Shotgun constructor. This can be useful if you # need to construct a Shotgun API instance based on # another Shotgun API instance. - self.raw_http_proxy = None + self.raw_http_proxy: Optional[str] = None # if a proxy server is being used, the proxy_handler # below will contain a urllib2.ProxyHandler instance # which can be used whenever a request needs to be made. - self.proxy_handler = None - self.proxy_server = None + self.proxy_handler: Optional["urllib.request.ProxyHandler"] = None + self.proxy_server: Optional[str] = None self.proxy_port = 8080 - self.proxy_user = None - self.proxy_pass = None - self.session_token = None - self.authorization = None + self.proxy_user: Optional[str] = None + self.proxy_pass: Optional[str] = None + self.session_token: Optional[str] = None + self.authorization: Optional[str] = None self.localized = False - def set_server_params(self, base_url): + def set_server_params(self, base_url: str) -> None: """ Set the different server related fields based on the passed in URL. @@ -432,7 +466,7 @@ def set_server_params(self, base_url): ) @property - def records_per_page(self): + def records_per_page(self) -> int: """ The records per page value from the server. """ @@ -465,19 +499,19 @@ class Shotgun(object): def __init__( self, - base_url, - script_name=None, - api_key=None, - convert_datetimes_to_utc=True, - http_proxy=None, - connect=True, - ca_certs=None, - login=None, - password=None, - sudo_as_login=None, - session_token=None, - auth_token=None, - ): + base_url: str, + script_name: Optional[str] = None, + api_key: Optional[str] = None, + convert_datetimes_to_utc: bool = True, + http_proxy: Optional[str] = None, + connect: bool = True, + ca_certs: Optional[str] = None, + login: Optional[str] = None, + password: Optional[str] = None, + sudo_as_login: Optional[str] = None, + session_token: Optional[str] = None, + auth_token: Optional[str] = None, + ) -> None: """ Initializes a new instance of the Shotgun client. @@ -589,7 +623,7 @@ def __init__( "must provide login/password, session_token or script_name/api_key" ) - self.config = _Config(self) + self.config: _Config = _Config(self) self.config.api_key = api_key self.config.script_name = script_name self.config.user_login = login @@ -625,7 +659,7 @@ def __init__( ): SHOTGUN_API_DISABLE_ENTITY_OPTIMIZATION = True - self._connection = None + self._connection: Optional[Http] = None self.__ca_certs = self._get_certs_file(ca_certs) @@ -690,7 +724,7 @@ def __init__( # this relies on self.client_caps being set first self.reset_user_agent() - self._server_caps = None + self._server_caps: Optional[ServerCapabilities] = None # test to ensure the the server supports the json API # call to server will only be made once and will raise error if connect: @@ -704,7 +738,7 @@ def __init__( self.config.user_password = None self.config.auth_token = None - def _split_url(self, base_url): + def _split_url(self, base_url: str) -> tuple[Optional[str], Optional[str]]: """ Extract the hostname:port and username/password/token from base_url sent when connect to the API. @@ -736,7 +770,7 @@ def _split_url(self, base_url): # API Functions @property - def server_info(self): + def server_info(self) -> dict[str, Any]: """ Property containing server information. @@ -754,7 +788,7 @@ def server_info(self): return self.server_caps.server_info @property - def server_caps(self): + def server_caps(self) -> ServerCapabilities: """ Property containing :class:`ServerCapabilities` object. @@ -769,7 +803,7 @@ def server_caps(self): self._server_caps = ServerCapabilities(self.config.server, self.info()) return self._server_caps - def connect(self): + def connect(self) -> None: """ Connect client to the server if it is not already connected. @@ -780,7 +814,7 @@ def connect(self): self.info() return - def close(self): + def close(self) -> None: """ Close the current connection to the server. @@ -789,7 +823,7 @@ def close(self): self._close_connection() return - def info(self): + def info(self) -> dict[str, Any]: """ Get API-related metadata from the Shotgun server. @@ -822,15 +856,15 @@ def info(self): def find_one( self, - entity_type, - filters, - fields=None, - order=None, - filter_operator=None, - retired_only=False, - include_archived_projects=True, - additional_filter_presets=None, - ): + entity_type: str, + filters: Union[list, tuple, dict[str, Any]], + fields: Optional[list[str]] = None, + order: Optional[list[OrderItem]] = None, + filter_operator: Optional[Literal["all", "any"]] = None, + retired_only: bool = False, + include_archived_projects: bool = True, + additional_filter_presets: Optional[list[dict[str, Any]]] = None, + ) -> Optional[BaseEntity]: """ Shortcut for :meth:`~shotgun_api3.Shotgun.find` with ``limit=1`` so it returns a single result. @@ -845,7 +879,7 @@ def find_one( :param list fields: Optional list of fields to include in each entity record returned. Defaults to ``["id"]``. - :param int order: Optional list of fields to order the results by. List has the format:: + :param list order: Optional list of fields to order the results by. List has the format:: [ {'field_name':'foo', 'direction':'asc'}, @@ -862,7 +896,7 @@ def find_one( same query. :param bool include_archived_projects: Optional boolean flag to include entities whose projects have been archived. Defaults to ``True``. - :param additional_filter_presets: Optional list of presets to further filter the result + :param list additional_filter_presets: Optional list of presets to further filter the result set, list has the form:: [{ @@ -902,17 +936,17 @@ def find_one( def find( self, - entity_type, - filters, - fields=None, - order=None, - filter_operator=None, - limit=0, - retired_only=False, - page=0, - include_archived_projects=True, - additional_filter_presets=None, - ): + entity_type: str, + filters: Union[list, tuple, dict[str, Any]], + fields: Optional[list[str]] = None, + order: Optional[list[OrderItem]] = None, + filter_operator: Optional[Literal["all", "any"]] = None, + limit: int = 0, + retired_only: bool = False, + page: int = 0, + include_archived_projects: bool = True, + additional_filter_presets: Optional[list[dict[str, Any]]] = None, + ) -> list[BaseEntity]: """ Find entities matching the given filters. @@ -990,7 +1024,7 @@ def find( same query. :param bool include_archived_projects: Optional boolean flag to include entities whose projects have been archived. Defaults to ``True``. - :param additional_filter_presets: Optional list of presets to further filter the result + :param list additional_filter_presets: Optional list of presets to further filter the result set, list has the form:: [{ @@ -1101,15 +1135,15 @@ def find( def _construct_read_parameters( self, - entity_type, - fields, - filters, - retired_only, - order, - include_archived_projects, - additional_filter_presets, - ): - params = {} + entity_type: str, + fields: Optional[list[str]], + filters: dict[str, Any], + retired_only: bool, + order: Optional[list[dict[str, Any]]], + include_archived_projects: bool, + additional_filter_presets: Optional[list[dict[str, Any]]], + ) -> dict[str, Any]: + params: dict[str, Any] = {} params["type"] = entity_type params["return_fields"] = fields or ["id"] params["filters"] = filters @@ -1139,7 +1173,9 @@ def _construct_read_parameters( params["sorts"] = sort_list return params - def _add_project_param(self, params, project_entity): + def _add_project_param( + self, params: dict[str, Any], project_entity + ) -> dict[str, Any]: if project_entity and self.server_caps.ensure_per_project_customization(): params["project"] = project_entity @@ -1147,8 +1183,12 @@ def _add_project_param(self, params, project_entity): return params def _translate_update_params( - self, entity_type, entity_id, data, multi_entity_update_modes - ): + self, + entity_type: str, + entity_id: int, + data: dict, + multi_entity_update_modes: Optional[dict], + ) -> dict[str, Any]: global SHOTGUN_API_DISABLE_ENTITY_OPTIMIZATION def optimize_field(field_dict): @@ -1170,13 +1210,13 @@ def optimize_field(field_dict): def summarize( self, - entity_type, - filters, - summary_fields, - filter_operator=None, - grouping=None, - include_archived_projects=True, - ): + entity_type: str, + filters: Union[list, dict[str, Any]], + summary_fields: list[dict[str, str]], + filter_operator: Optional[str] = None, + grouping: Optional[list[GroupingItem]] = None, + include_archived_projects: bool = True, + ) -> dict[str, Any]: """ Summarize field data returned by a query. @@ -1376,7 +1416,12 @@ def summarize( records = self._call_rpc("summarize", params) return records - def create(self, entity_type, data, return_fields=None): + def create( + self, + entity_type: str, + data: dict[str, Any], + return_fields: Optional[list] = None, + ) -> dict[str, Any]: """ Create a new entity of the specified ``entity_type``. @@ -1459,7 +1504,13 @@ def create(self, entity_type, data, return_fields=None): return result - def update(self, entity_type, entity_id, data, multi_entity_update_modes=None): + def update( + self, + entity_type: str, + entity_id: int, + data: dict[str, Any], + multi_entity_update_modes: Optional[dict[str, Any]] = None, + ) -> BaseEntity: """ Update the specified entity with the supplied data. @@ -1538,7 +1589,7 @@ def update(self, entity_type, entity_id, data, multi_entity_update_modes=None): return result - def delete(self, entity_type, entity_id): + def delete(self, entity_type: str, entity_id: int) -> bool: """ Retire the specified entity. @@ -1562,7 +1613,7 @@ def delete(self, entity_type, entity_id): return self._call_rpc("delete", params) - def revive(self, entity_type, entity_id): + def revive(self, entity_type: str, entity_id: int) -> bool: """ Revive an entity that has previously been deleted. @@ -1580,7 +1631,7 @@ def revive(self, entity_type, entity_id): return self._call_rpc("revive", params) - def batch(self, requests): + def batch(self, requests: list[dict[str, Any]]) -> list[dict[str, Any]]: """ Make a batch request of several :meth:`~shotgun_api3.Shotgun.create`, :meth:`~shotgun_api3.Shotgun.update`, and :meth:`~shotgun_api3.Shotgun.delete` calls. @@ -1695,7 +1746,13 @@ def _required_keys(message, required_keys, data): records = self._call_rpc("batch", calls) return self._parse_records(records) - def work_schedule_read(self, start_date, end_date, project=None, user=None): + def work_schedule_read( + self, + start_date: str, + end_date: str, + project: Optional[dict[str, Any]] = None, + user: Optional[dict[str, Any]] = None, + ) -> dict[str, Any]: """ Return the work day rules for a given date range. @@ -1766,13 +1823,13 @@ def work_schedule_read(self, start_date, end_date, project=None, user=None): def work_schedule_update( self, - date, - working, - description=None, - project=None, - user=None, - recalculate_field=None, - ): + date: str, + working: bool, + description: Optional[str] = None, + project: Optional[dict[str, Any]] = None, + user: Optional[dict[str, Any]] = None, + recalculate_field: Optional[str] = None, + ) -> dict[str, Any]: """ Update the work schedule for a given date. @@ -1826,7 +1883,7 @@ def work_schedule_update( return self._call_rpc("work_schedule_update", params) - def follow(self, user, entity): + def follow(self, user: dict[str, Any], entity: dict[str, Any]) -> dict[str, Any]: """ Add the entity to the user's followed entities. @@ -1854,7 +1911,7 @@ def follow(self, user, entity): return self._call_rpc("follow", params) - def unfollow(self, user, entity): + def unfollow(self, user: dict[str, Any], entity: dict[str, Any]) -> dict[str, Any]: """ Remove entity from the user's followed entities. @@ -1881,7 +1938,7 @@ def unfollow(self, user, entity): return self._call_rpc("unfollow", params) - def followers(self, entity): + def followers(self, entity: dict[str, Any]) -> list[dict[str, Any]]: """ Return all followers for an entity. @@ -1909,7 +1966,12 @@ def followers(self, entity): return self._call_rpc("followers", params) - def following(self, user, project=None, entity_type=None): + def following( + self, + user: dict[str, Any], + project: Optional[dict[str, Any]] = None, + entity_type: Optional[str] = None, + ) -> list[BaseEntity]: """ Return all entity instances a user is following. @@ -1940,7 +2002,9 @@ def following(self, user, project=None, entity_type=None): return self._call_rpc("following", params) - def schema_entity_read(self, project_entity=None): + def schema_entity_read( + self, project_entity: Optional[BaseEntity] = None + ) -> dict[str, dict[str, Any]]: """ Return all active entity types, their display names, and their visibility. @@ -1975,7 +2039,7 @@ def schema_entity_read(self, project_entity=None): The returned display names for this method will be localized when the ``localize`` Shotgun config property is set to ``True``. See :ref:`localization` for more information. """ - params = {} + params: dict[str, Any] = {} params = self._add_project_param(params, project_entity) @@ -1984,7 +2048,9 @@ def schema_entity_read(self, project_entity=None): else: return self._call_rpc("schema_entity_read", None) - def schema_read(self, project_entity=None): + def schema_read( + self, project_entity: Optional[BaseEntity] = None + ) -> dict[str, dict[str, Any]]: """ Get the schema for all fields on all entities. @@ -2047,7 +2113,7 @@ def schema_read(self, project_entity=None): The returned display names for this method will be localized when the ``localize`` Shotgun config property is set to ``True``. See :ref:`localization` for more information. """ - params = {} + params: dict[str, Any] = {} params = self._add_project_param(params, project_entity) @@ -2056,7 +2122,12 @@ def schema_read(self, project_entity=None): else: return self._call_rpc("schema_read", None) - def schema_field_read(self, entity_type, field_name=None, project_entity=None): + def schema_field_read( + self, + entity_type: str, + field_name: Optional[str] = None, + project_entity: Optional[BaseEntity] = None, + ) -> dict[str, dict[str, Any]]: """ Get schema for all fields on the specified entity type or just the field name specified if provided. @@ -2121,8 +2192,12 @@ def schema_field_read(self, entity_type, field_name=None, project_entity=None): return self._call_rpc("schema_field_read", params) def schema_field_create( - self, entity_type, data_type, display_name, properties=None - ): + self, + entity_type: str, + data_type: str, + display_name: str, + properties: Optional[dict[str, Any]] = None, + ) -> str: """ Create a field for the specified entity type. @@ -2160,8 +2235,12 @@ def schema_field_create( return self._call_rpc("schema_field_create", params) def schema_field_update( - self, entity_type, field_name, properties, project_entity=None - ): + self, + entity_type: str, + field_name: str, + properties: dict[str, Any], + project_entity: Optional[BaseEntity] = None, + ) -> bool: """ Update the properties for the specified field on an entity. @@ -2175,9 +2254,9 @@ def schema_field_update( >>> sg.schema_field_update("Asset", "sg_test_number", properties) True - :param entity_type: Entity type of field to update. - :param field_name: Internal Shotgun name of the field to update. - :param properties: Dictionary with key/value pairs where the key is the property to be + :param str entity_type: Entity type of field to update. + :param str field_name: Internal Shotgun name of the field to update. + :param dict properties: Dictionary with key/value pairs where the key is the property to be updated and the value is the new value. :param dict project_entity: Optional Project entity specifying which project to modify the ``visible`` property for. If ``visible`` is present in ``properties`` and @@ -2202,7 +2281,7 @@ def schema_field_update( params = self._add_project_param(params, project_entity) return self._call_rpc("schema_field_update", params) - def schema_field_delete(self, entity_type, field_name): + def schema_field_delete(self, entity_type: str, field_name: str) -> bool: """ Delete the specified field from the entity type. @@ -2219,7 +2298,7 @@ def schema_field_delete(self, entity_type, field_name): return self._call_rpc("schema_field_delete", params) - def add_user_agent(self, agent): + def add_user_agent(self, agent: str) -> None: """ Add agent to the user-agent header. @@ -2231,7 +2310,7 @@ def add_user_agent(self, agent): """ self._user_agents.append(agent) - def reset_user_agent(self): + def reset_user_agent(self) -> None: """ Reset user agent to the default value. @@ -2251,7 +2330,7 @@ def reset_user_agent(self): "ssl %s" % (self.client_caps.ssl_version), ] - def set_session_uuid(self, session_uuid): + def set_session_uuid(self, session_uuid: str) -> None: """ Set the browser session_uuid in the current Shotgun API instance. @@ -2269,12 +2348,12 @@ def set_session_uuid(self, session_uuid): def share_thumbnail( self, - entities, - thumbnail_path=None, - source_entity=None, - filmstrip_thumbnail=False, - **kwargs, - ): + entities: list[dict[str, Any]], + thumbnail_path: Optional[str] = None, + source_entity: Optional[BaseEntity] = None, + filmstrip_thumbnail: bool = False, + **kwargs: Any, + ) -> int: """ Associate a thumbnail with more than one Shotgun entity. @@ -2413,7 +2492,9 @@ def share_thumbnail( return attachment_id - def upload_thumbnail(self, entity_type, entity_id, path, **kwargs): + def upload_thumbnail( + self, entity_type: str, entity_id: int, path: str, **kwargs: Any + ) -> int: """ Upload a file from a local path and assign it as the thumbnail for the specified entity. @@ -2438,12 +2519,15 @@ def upload_thumbnail(self, entity_type, entity_id, path, **kwargs): :param int entity_id: Id of the entity to set the thumbnail for. :param str path: Full path to the thumbnail file on disk. :returns: Id of the new attachment + :rtype: int """ return self.upload( entity_type, entity_id, path, field_name="thumb_image", **kwargs ) - def upload_filmstrip_thumbnail(self, entity_type, entity_id, path, **kwargs): + def upload_filmstrip_thumbnail( + self, entity_type: str, entity_id: int, path: str, **kwargs: Any + ) -> int: """ Upload filmstrip thumbnail to specified entity. @@ -2494,13 +2578,13 @@ def upload_filmstrip_thumbnail(self, entity_type, entity_id, path, **kwargs): def upload( self, - entity_type, - entity_id, - path, - field_name=None, - display_name=None, - tag_list=None, - ): + entity_type: str, + entity_id: int, + path: str, + field_name: Optional[str] = None, + display_name: Optional[str] = None, + tag_list: Optional[str] = None, + ) -> int: """ Upload a file to the specified entity. @@ -2583,14 +2667,14 @@ def upload( def _upload_to_storage( self, - entity_type, - entity_id, - path, - field_name, - display_name, - tag_list, - is_thumbnail, - ): + entity_type: str, + entity_id: int, + path: str, + field_name: Optional[str], + display_name: Optional[str], + tag_list: Optional[str], + is_thumbnail: bool, + ) -> int: """ Internal function to upload a file to the Cloud storage and link it to the specified entity. @@ -2673,14 +2757,14 @@ def _upload_to_storage( def _upload_to_sg( self, - entity_type, - entity_id, - path, - field_name, - display_name, - tag_list, - is_thumbnail, - ): + entity_type: str, + entity_id: int, + path: str, + field_name: Optional[str], + display_name: Optional[str], + tag_list: Optional[str], + is_thumbnail: bool, + ) -> int: """ Internal function to upload a file to Shotgun and link it to the specified entity. @@ -2752,7 +2836,9 @@ def _upload_to_sg( attachment_id = int(result.split(":", 2)[1].split("\n", 1)[0]) return attachment_id - def _get_attachment_upload_info(self, is_thumbnail, filename, is_multipart_upload): + def _get_attachment_upload_info( + self, is_thumbnail: bool, filename: str, is_multipart_upload: bool + ) -> dict[str, Any]: """ Internal function to get the information needed to upload a file to Cloud storage. @@ -2799,7 +2885,12 @@ def _get_attachment_upload_info(self, is_thumbnail, filename, is_multipart_uploa "upload_info": upload_info, } - def download_attachment(self, attachment=False, file_path=None, attachment_id=None): + def download_attachment( + self, + attachment: Union[dict[str, Any], Literal[False]] = False, + file_path: Optional[str] = None, + attachment_id: Optional[int] = None, + ) -> Union[str, bytes, None]: """ Download the file associated with a Shotgun Attachment. @@ -2915,7 +3006,7 @@ def download_attachment(self, attachment=False, file_path=None, attachment_id=No else: return attachment - def get_auth_cookie_handler(self): + def get_auth_cookie_handler(self) -> urllib.request.HTTPCookieProcessor: """ Return an urllib cookie handler containing a cookie for FPTR authentication. @@ -2947,7 +3038,9 @@ def get_auth_cookie_handler(self): cj.set_cookie(c) return urllib.request.HTTPCookieProcessor(cj) - def get_attachment_download_url(self, attachment): + def get_attachment_download_url( + self, attachment: Optional[Union[int, dict[str, Any]]] + ) -> str: """ Return the URL for downloading provided Attachment. @@ -3005,7 +3098,9 @@ def get_attachment_download_url(self, attachment): ) return url - def authenticate_human_user(self, user_login, user_password, auth_token=None): + def authenticate_human_user( + self, user_login: str, user_password: str, auth_token: Optional[str] = None + ) -> Union[dict[str, Any], None]: """ Authenticate Shotgun HumanUser. @@ -3064,7 +3159,9 @@ def authenticate_human_user(self, user_login, user_password, auth_token=None): self.config.auth_token = original_auth_token raise - def update_project_last_accessed(self, project, user=None): + def update_project_last_accessed( + self, project: dict[str, Any], user: Optional[dict[str, Any]] = None + ) -> None: """ Update a Project's ``last_accessed_by_current_user`` field to the current timestamp. @@ -3110,7 +3207,9 @@ def update_project_last_accessed(self, project, user=None): record = self._call_rpc("update_project_last_accessed_by_current_user", params) self._parse_records(record)[0] - def note_thread_read(self, note_id, entity_fields=None): + def note_thread_read( + self, note_id: int, entity_fields: Optional[dict[str, Any]] = None + ) -> list[dict[str, Any]]: """ Return the full conversation for a given note, including Replies and Attachments. @@ -3185,7 +3284,13 @@ def note_thread_read(self, note_id, entity_fields=None): result = self._parse_records(record) return result - def text_search(self, text, entity_types, project_ids=None, limit=None): + def text_search( + self, + text: str, + entity_types: dict[str, Any], + project_ids: Optional[list] = None, + limit: Optional[int] = None, + ) -> dict[str, Any]: """ Search across the specified entity types for the given text. @@ -3279,13 +3384,13 @@ def text_search(self, text, entity_types, project_ids=None, limit=None): def activity_stream_read( self, - entity_type, - entity_id, - entity_fields=None, - min_id=None, - max_id=None, - limit=None, - ): + entity_type: str, + entity_id: int, + entity_fields: Optional[dict[str, Any]] = None, + min_id: Optional[int] = None, + max_id: Optional[int] = None, + limit: Optional[int] = None, + ) -> dict[str, Any]: """ Retrieve activity stream data from Shotgun. @@ -3375,7 +3480,7 @@ def activity_stream_read( result = self._parse_records(record)[0] return result - def nav_expand(self, path, seed_entity_field=None, entity_fields=None): + def nav_expand(self, path: str, seed_entity_field=None, entity_fields=None): """ Expand the navigation hierarchy for the supplied path. @@ -3395,7 +3500,9 @@ def nav_expand(self, path, seed_entity_field=None, entity_fields=None): }, ) - def nav_search_string(self, root_path, search_string, seed_entity_field=None): + def nav_search_string( + self, root_path: str, search_string: str, seed_entity_field=None + ): """ Search function adapted to work with the navigation hierarchy. @@ -3414,7 +3521,12 @@ def nav_search_string(self, root_path, search_string, seed_entity_field=None): }, ) - def nav_search_entity(self, root_path, entity, seed_entity_field=None): + def nav_search_entity( + self, + root_path: str, + entity: dict[str, Any], + seed_entity_field: Optional[dict[str, Any]] = None, + ): """ Search function adapted to work with the navigation hierarchy. @@ -3434,7 +3546,7 @@ def nav_search_entity(self, root_path, entity, seed_entity_field=None): }, ) - def get_session_token(self): + def get_session_token(self) -> str: """ Get the session token associated with the current session. @@ -3458,7 +3570,7 @@ def get_session_token(self): return session_token - def preferences_read(self, prefs=None): + def preferences_read(self, prefs: Optional[list] = None) -> dict[str, Any]: """ Get a subset of the site preferences. @@ -3481,7 +3593,7 @@ def preferences_read(self, prefs=None): return self._call_rpc("preferences_read", {"prefs": prefs}) - def user_subscriptions_read(self): + def user_subscriptions_read(self) -> list: """ Get the list of user subscriptions. @@ -3493,8 +3605,9 @@ def user_subscriptions_read(self): return self._call_rpc("user_subscriptions_read", None) - def user_subscriptions_create(self, users): - # type: (list[dict[str, Union[str, list[str], None]) -> bool + def user_subscriptions_create( + self, users: list[dict[str, Union[str, list[str], None]]] + ) -> bool: """ Assign subscriptions to users. @@ -3515,7 +3628,7 @@ def user_subscriptions_create(self, users): return response.get("status") == "success" - def _build_opener(self, handler): + def _build_opener(self, handler) -> urllib.request.OpenerDirector: """ Build urllib2 opener with appropriate proxy handler. """ @@ -3616,7 +3729,13 @@ def entity_types(self): # ======================================================================== # RPC Functions - def _call_rpc(self, method, params, include_auth_params=True, first=False): + def _call_rpc( + self, + method: str, + params: Any, + include_auth_params: bool = True, + first: bool = False, + ) -> Any: """ Call the specified method on the Shotgun Server sending the supplied payload. """ @@ -3680,7 +3799,7 @@ def _call_rpc(self, method, params, include_auth_params=True, first=False): return results[0] return results - def _auth_params(self): + def _auth_params(self) -> dict[str, Any]: """ Return a dictionary of the authentication parameters being used. """ @@ -3735,7 +3854,7 @@ def _auth_params(self): return auth_params - def _sanitize_auth_params(self, params): + def _sanitize_auth_params(self, params: dict[str, Any]) -> dict[str, Any]: """ Given an authentication parameter dictionary, sanitize any sensitive information and return the sanitized dict copy. @@ -3746,7 +3865,9 @@ def _sanitize_auth_params(self, params): sanitized_params[k] = "********" return sanitized_params - def _build_payload(self, method, params, include_auth_params=True): + def _build_payload( + self, method: str, params, include_auth_params: bool = True + ) -> dict[str, Any]: """ Build the payload to be send to the rpc endpoint. """ @@ -3764,7 +3885,7 @@ def _build_payload(self, method, params, include_auth_params=True): return {"method_name": method, "params": call_params} - def _encode_payload(self, payload): + def _encode_payload(self, payload) -> bytes: """ Encode the payload to a string to be passed to the rpc endpoint. @@ -3775,7 +3896,9 @@ def _encode_payload(self, payload): return json.dumps(payload, ensure_ascii=False).encode("utf-8") - def _make_call(self, verb, path, body, headers): + def _make_call( + self, verb: str, path: str, body, headers: Optional[dict[str, Any]] + ) -> tuple[tuple[int, str], dict[str, Any], str]: """ Make an HTTP call to the server. @@ -3825,7 +3948,9 @@ def _make_call(self, verb, path, body, headers): ) time.sleep(rpc_attempt_interval) - def _http_request(self, verb, path, body, headers): + def _http_request( + self, verb: str, path: str, body, headers: dict[str, Any] + ) -> tuple[tuple[int, str], dict[str, Any], str]: """ Make the actual HTTP request. """ @@ -3849,7 +3974,9 @@ def _http_request(self, verb, path, body, headers): return (http_status, resp_headers, resp_body) - def _make_upload_request(self, request, opener): + def _make_upload_request( + self, request, opener: "urllib.request.OpenerDirector" + ) -> "urllib.request._UrlopenRet": """ Open the given request object, return the response, raises URLError on protocol errors. @@ -3861,7 +3988,7 @@ def _make_upload_request(self, request, opener): raise return result - def _parse_http_status(self, status): + def _parse_http_status(self, status: tuple) -> None: """ Parse the status returned from the http request. @@ -3879,7 +4006,9 @@ def _parse_http_status(self, status): return - def _decode_response(self, headers, body): + def _decode_response( + self, headers: dict[str, Any], body: str + ) -> Union[str, dict[str, Any]]: """ Decode the response from the server from the wire format to a python data structure. @@ -3900,7 +4029,7 @@ def _decode_response(self, headers, body): return self._json_loads(body) return body - def _json_loads(self, body): + def _json_loads(self, body: str) -> Any: return json.loads(body) def _response_errors(self, sg_response): @@ -3949,7 +4078,7 @@ def _response_errors(self, sg_response): raise Fault(sg_response.get("message", "Unknown Error")) return - def _visit_data(self, data, visitor): + def _visit_data(self, data: T, visitor) -> T: """ Walk the data (simple python types) and call the visitor. """ @@ -3959,17 +4088,17 @@ def _visit_data(self, data, visitor): recursive = self._visit_data if isinstance(data, list): - return [recursive(i, visitor) for i in data] + return [recursive(i, visitor) for i in data] # type: ignore[return-value] if isinstance(data, tuple): - return tuple(recursive(i, visitor) for i in data) + return tuple(recursive(i, visitor) for i in data) # type: ignore[return-value] if isinstance(data, dict): - return dict((k, recursive(v, visitor)) for k, v in data.items()) + return dict((k, recursive(v, visitor)) for k, v in data.items()) # type: ignore[return-value] return visitor(data) - def _transform_outbound(self, data): + def _transform_outbound(self, data: T) -> T: """ Transform data types or values before they are sent by the client. @@ -4016,7 +4145,7 @@ def _outbound_visitor(value): return self._visit_data(data, _outbound_visitor) - def _transform_inbound(self, data): + def _transform_inbound(self, data: T) -> T: """ Transforms data types or values after they are received from the server. """ @@ -4052,7 +4181,7 @@ def _inbound_visitor(value): # ======================================================================== # Connection Functions - def _get_connection(self): + def _get_connection(self) -> Http: """ Return the current connection or creates a new connection to the current server. """ @@ -4081,7 +4210,7 @@ def _get_connection(self): return self._connection - def _close_connection(self): + def _close_connection(self) -> None: """ Close the current connection. """ @@ -4100,7 +4229,7 @@ def _close_connection(self): # ======================================================================== # Utility - def _parse_records(self, records): + def _parse_records(self, records: list) -> list: """ Parse 'records' returned from the api to do local modifications: @@ -4156,7 +4285,7 @@ def _parse_records(self, records): return records - def _build_thumb_url(self, entity_type, entity_id): + def _build_thumb_url(self, entity_type: str, entity_id: int) -> str: """ Return the URL for the thumbnail of an entity given the entity type and the entity id. @@ -4204,8 +4333,12 @@ def _build_thumb_url(self, entity_type, entity_id): raise RuntimeError("Unknown code %s %s" % (code, thumb_url)) def _dict_to_list( - self, d, key_name="field_name", value_name="value", extra_data=None - ): + self, + d: Optional[dict[str, Any]], + key_name: str = "field_name", + value_name: str = "value", + extra_data=None, + ) -> list[dict[str, Any]]: """ Utility function to convert a dict into a list dicts using the key_name and value_name keys. @@ -4222,7 +4355,7 @@ def _dict_to_list( ret.append(d) return ret - def _dict_to_extra_data(self, d, key_name="value"): + def _dict_to_extra_data(self, d: Optional[dict], key_name="value") -> dict: """ Utility function to convert a dict into a dict compatible with the extra_data arg of _dict_to_list. @@ -4231,7 +4364,7 @@ def _dict_to_extra_data(self, d, key_name="value"): """ return dict([(k, {key_name: v}) for (k, v) in (d or {}).items()]) - def _upload_file_to_storage(self, path, storage_url): + def _upload_file_to_storage(self, path: str, storage_url: str) -> None: """ Internal function to upload an entire file to the Cloud storage. @@ -4251,7 +4384,9 @@ def _upload_file_to_storage(self, path, storage_url): LOG.debug("File uploaded to Cloud storage: %s", filename) - def _multipart_upload_file_to_storage(self, path, upload_info): + def _multipart_upload_file_to_storage( + self, path: str, upload_info: dict[str, Any] + ) -> None: """ Internal function to upload a file to the Cloud storage in multiple parts. @@ -4293,7 +4428,9 @@ def _multipart_upload_file_to_storage(self, path, upload_info): LOG.debug("File uploaded in multiple parts to Cloud storage: %s", path) - def _get_upload_part_link(self, upload_info, filename, part_number): + def _get_upload_part_link( + self, upload_info: dict[str, Any], filename: str, part_number: int + ) -> str: """ Internal function to get the url to upload the next part of a file to the Cloud storage, in a multi-part upload process. @@ -4333,7 +4470,9 @@ def _get_upload_part_link(self, upload_info, filename, part_number): LOG.debug("Got next upload link from server for multipart upload.") return result.split("\n", 2)[1] - def _upload_data_to_storage(self, data, content_type, size, storage_url): + def _upload_data_to_storage( + self, data: BinaryIO, content_type: str, size: int, storage_url: str + ) -> str: """ Internal function to upload data to Cloud storage. @@ -4388,13 +4527,15 @@ def _upload_data_to_storage(self, data, content_type, size, storage_url): LOG.debug("Part upload completed successfully.") return etag - def _complete_multipart_upload(self, upload_info, filename, etags): + def _complete_multipart_upload( + self, upload_info: dict[str, Any], filename: str, etags: Iterable[str] + ) -> None: """ Internal function to complete a multi-part upload to the Cloud storage. :param dict upload_info: Contains details received from the server, about the upload. :param str filename: Name of the file for which we want to complete the upload. - :param tupple etags: Contains the etag of each uploaded file part. + :param tuple etags: Contains the etag of each uploaded file part. """ params = { @@ -4421,7 +4562,9 @@ def _complete_multipart_upload(self, upload_info, filename, etags): if not result.startswith("1"): raise ShotgunError("Unable get upload part link: %s" % result) - def _requires_direct_s3_upload(self, entity_type, field_name): + def _requires_direct_s3_upload( + self, entity_type: str, field_name: Optional[str] + ) -> bool: """ Internal function that determines if an entity_type + field_name combination should be uploaded to cloud storage. @@ -4462,7 +4605,7 @@ def _requires_direct_s3_upload(self, entity_type, field_name): else: return False - def _send_form(self, url, params): + def _send_form(self, url: str, params: dict[str, Any]) -> str: """ Utility function to send a Form to Shotgun and process any HTTP errors that could occur. @@ -4594,7 +4737,7 @@ def https_request(self, request): return self.http_request(request) -def _translate_filters(filters, filter_operator): +def _translate_filters(filters: Union[list, tuple], filter_operator) -> dict[str, Any]: """ Translate filters params into data structure expected by rpc call. """ @@ -4603,7 +4746,7 @@ def _translate_filters(filters, filter_operator): return _translate_filters_dict(wrapped_filters) -def _translate_filters_dict(sg_filter): +def _translate_filters_dict(sg_filter: dict[str, Any]) -> dict[str, Any]: new_filters = {} filter_operator = sg_filter.get("filter_operator") @@ -4663,7 +4806,7 @@ def _translate_filters_simple(sg_filter): return condition -def _version_str(version): +def _version_str(version) -> str: """ Convert a tuple of int's to a '.' separated str. """ From b0df9fe683b4510e69f5b77c50d5d0ce233be03e Mon Sep 17 00:00:00 2001 From: Julien Langlois <16244608+julien-lang@users.noreply.github.com> Date: Wed, 12 Nov 2025 12:01:53 -0800 Subject: [PATCH 114/125] SG-40841 Add information about relative_path field (#420) * Add information about relative_path field * Update docs/cookbook/attachments.rst * Add missing trailing comma for consistence --- docs/cookbook/attachments.rst | 63 ++++++++++++++++++++++------------- 1 file changed, 40 insertions(+), 23 deletions(-) diff --git a/docs/cookbook/attachments.rst b/docs/cookbook/attachments.rst index de992431d..cfe609de8 100644 --- a/docs/cookbook/attachments.rst +++ b/docs/cookbook/attachments.rst @@ -124,17 +124,22 @@ will vary. :: - { 'content_type': 'video/quicktime', + { + 'content_type': 'video/quicktime', 'link_type': 'local', + 'local_path': '/Users/kp/Movies/testing/test_movie_002.mov', + 'local_path_linux': '/home/users/macusers/kp/Movies/testing/test_movie_002.mov', + 'local_path_mac': '/Users/kp/Movies/testing/test_movie_002.mov', + 'local_path_windows': 'M:\\macusers\\kp\\Movies\\testing\\test_movie_002.mov', + 'local_storage': { + 'id': 1, + 'name': 'Dailies Directories', + 'type': 'LocalStorage', + }, 'name': 'my_test_movie.mov', - 'local_path': '/Users/kp/Movies/testing/test_movie_002.mov' - 'local_path_linux': '/home/users/macusers/kp/Movies/testing/test_movie_002.mov' - 'local_path_mac': '/Users/kp/Movies/testing/test_movie_002.mov' - 'local_path_windows': 'M:\\macusers\kp\Movies\testing\test_movie_002.mov' - 'local_storage': {'id': 1, - 'name': 'Dailies Directories', - 'type': 'LocalStorage'}, - 'url': 'file:///Users/kp/Movies/testing/test_movie_002.mov'} + 'relative_path': 'testing/test_movie_002.mov', + 'url': 'file:///Users/kp/Movies/testing/test_movie_002.mov', + } ******************** @@ -237,6 +242,9 @@ are available: - **local_storage** (:obj:`dict`) *read-only*: A dictionary representing which LocalStorage entity is applied for this local file link. +- **relative_path** (:obj:`str`) *read-only*: + The path to the file relative to the ``local_storage`` root. + - **url** (:obj:`str`) *read-only*: A file URI (``file://``) path provided for convenience pointing to the value in the ``local_path`` @@ -250,19 +258,26 @@ Reading Local File Fields Returns:: - {'id':123, - 'sg_uploaded_movie': { 'content_type': None, - 'link_type': 'local', - 'name': 'my_test_movie.mov', - 'local_path': '/Users/kp/Movies/testing/test_movie_001_.mov' - 'local_path_linux': '/home/users/macusers/kp/Movies/testing/test_movie_001_.mov' - 'local_path_mac': '/Users/kp/Movies/testing/test_movie_001_.mov' - 'local_path_windows': 'M:\\macusers\kp\Movies\testing\test_movie_001_.mov' - 'local_storage': {'id': 1, - 'name': 'Dailies Directories', - 'type': 'LocalStorage'}, - 'url': 'file:///Users/kp/Movies/testing/test_movie_001_.mov'}, - 'type': 'Version'} + { + 'id': 123, + 'sg_uploaded_movie': { + 'content_type': None, + 'link_type': 'local', + 'local_path': '/Users/kp/Movies/testing/test_movie_001_.mov', + 'local_path_linux': '/home/users/macusers/kp/Movies/testing/test_movie_001_.mov', + 'local_path_mac': '/Users/kp/Movies/testing/test_movie_001_.mov', + 'local_path_windows': 'M:\\macusers\\kp\\Movies\\testing\\test_movie_001_.mov', + 'local_storage': { + 'id': 1, + 'name': 'Dailies Directories', + 'type': 'LocalStorage', + }, + 'relative_path': 'testing/test_movie_001_.mov', + 'name': 'my_test_movie.mov', + 'url': 'file:///Users/kp/Movies/testing/test_movie_001_.mov', + }, + 'type': 'Version', + } .. note:: When viewing results that include file/link fields with local file link values, all of the @@ -335,7 +350,8 @@ Returns:: 'name': 'Dailies Directories', 'type': 'LocalStorage' }, - 'url': 'file:///Users/kp/Movies/testing/test_movie_002.mov' + 'relative_path': 'testing/test_movie_002.mov', + 'url': 'file:///Users/kp/Movies/testing/test_movie_002.mov', }, 'type': 'Version', } @@ -379,6 +395,7 @@ Returns:: 'name': 'Dailies Directories', 'type': 'LocalStorage' }, + 'relative_path': 'testing/test_movie_002.mov', 'url': 'file:///Users/kp/Movies/testing/test_movie_002.mov' }, 'type': 'Version', From 3b30ce4d6f7c34b2d0e38009421b652ff66351f4 Mon Sep 17 00:00:00 2001 From: Carlos Villavicencio Date: Thu, 20 Nov 2025 14:09:27 -0500 Subject: [PATCH 115/125] SG-39225 Support specific keys for entity optimization (#423) * Support specific keys for entity optimization * Update conditions * Update test * Support lists without dicts on optimization * Improved readability * Improvements by feedback * More granular optimization * Use simple conditions instead of set intersection * Include id and type checks * Make recursive and add more test cases --- shotgun_api3/shotgun.py | 41 ++++++++++++++++--------- tests/test_unit.py | 67 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 88 insertions(+), 20 deletions(-) diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index 0c0c9cd5c..2a1108aac 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -1194,7 +1194,7 @@ def _translate_update_params( def optimize_field(field_dict): if SHOTGUN_API_DISABLE_ENTITY_OPTIMIZATION: return field_dict - return {k: _get_type_and_id_from_value(v) for k, v in field_dict.items()} + return {k: _optimize_filter_field(v) for k, v in field_dict.items()} full_fields = self._dict_to_list( data, @@ -4799,7 +4799,7 @@ def _translate_filters_simple(sg_filter): and condition["relation"] in ["is", "is_not", "in", "not_in"] and isinstance(values[0], dict) ): - values = [_get_type_and_id_from_value(v) for v in values] + values = [_optimize_filter_field(v) for v in values] condition["values"] = values @@ -4813,17 +4813,30 @@ def _version_str(version) -> str: return ".".join(map(str, version)) -def _get_type_and_id_from_value(value): +def _optimize_filter_field( + field_value: Union[dict, list], recursive: bool = True +) -> Union[dict, list]: """ - For an entity dictionary, returns a new dictionary with only the type and id keys. - If any of these keys are not present, the original dictionary is returned. + For an FPT entity, returns a new dictionary with only the type, + id, and other allowed keys. + If case of any processing error, the original dictionary is returned. + + At least `type` and `id` keys are required to do the optimization """ - try: - if isinstance(value, dict): - return {"type": value["type"], "id": value["id"]} - elif isinstance(value, list): - return [{"type": v["type"], "id": v["id"]} for v in value] - except (KeyError, TypeError): - LOG.debug(f"Could not optimize entity value {value}") - - return value + allowed_keys = { + "id", + "type", + "url", + "name", + "content_type", + "local_path", + "storage", + "relative_path", + } + if isinstance(field_value, dict) and "id" in field_value and "type" in field_value: + return {key: field_value[key] for key in allowed_keys if key in field_value} + + elif recursive and isinstance(field_value, list): + return [_optimize_filter_field(fv, recursive=False) for fv in field_value] + + return field_value diff --git a/tests/test_unit.py b/tests/test_unit.py index 42f882af4..786a83f02 100644 --- a/tests/test_unit.py +++ b/tests/test_unit.py @@ -673,15 +673,41 @@ def test_related_object_update_optimization_entity_multi(self): entity_id = 6626 data = { "sg_status_list": "ip", - "project": {"id": 70, "type": "Project", "name": "disposable name 70"}, + "project": {"id": 70, "type": "Project", "name": "important name 70"}, "sg_vvv": [ - {"id": 6441, "type": "Asset", "name": "disposable name 6441"}, {"id": 6440, "type": "Asset"}, + {"id": 6441, "type": "Asset", "custom_name": "disposable name 6441"}, + { + # To be kept + "id": 6442, + "type": "Asset", + "url": "http://test.com/asset/6442", + # to be removed + "custom_name": "disposable name 1", + "custom_name2": "disposable name 2", + "custom_name3": "disposable name 3", + "custom_name4": "disposable name 4", + }, + { + "sg_nested": { + "level1": { + "level2": {"id": 123, "type": "Entity", "foo": "bar"} + } + } + }, ], "sg_class": { + # To be kept "id": 1, "type": "CustomEntity53", - "name": "disposable name 1", + "url": "http://test.com", + "name": "important class name", + "local_path": "/some/local/path", + # to be removed + "custom_name": "disposable name 1", + "custom_name2": "disposable name 2", + "custom_name3": "disposable name 3", + "custom_name4": "disposable name 4", }, } expected = { @@ -689,17 +715,46 @@ def test_related_object_update_optimization_entity_multi(self): "id": 6626, "fields": [ {"field_name": "sg_status_list", "value": "ip"}, - {"field_name": "project", "value": {"type": "Project", "id": 70}}, + { + "field_name": "project", + "value": { + "type": "Project", + "id": 70, + "name": "important name 70", + }, + }, { "field_name": "sg_vvv", "value": [ - {"id": 6441, "type": "Asset"}, {"id": 6440, "type": "Asset"}, + {"id": 6441, "type": "Asset"}, + { + "id": 6442, + "type": "Asset", + "url": "http://test.com/asset/6442", + }, + { + "sg_nested": { + "level1": { + "level2": { + "id": 123, + "type": "Entity", + "foo": "bar", + } + } + } + }, ], }, { "field_name": "sg_class", - "value": {"type": "CustomEntity53", "id": 1}, + "value": { + "type": "CustomEntity53", + "id": 1, + "name": "important class name", + "url": "http://test.com", + "local_path": "/some/local/path", + }, }, ], } From 9e359839b1a09de152e9ddecd2540716e1ea0297 Mon Sep 17 00:00:00 2001 From: Carlos Villavicencio Date: Tue, 25 Nov 2025 12:43:15 -0500 Subject: [PATCH 116/125] SG-41239 Make type annotations compatible with py37 (#427) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Make type annotations compatible with py37 🫠 * Fake type check on CI --- azure-pipelines-templates/type_checking.yml | 48 ++++ azure-pipelines.yml | 1 + shotgun_api3/shotgun.py | 229 ++++++++++---------- 3 files changed, 168 insertions(+), 110 deletions(-) create mode 100644 azure-pipelines-templates/type_checking.yml diff --git a/azure-pipelines-templates/type_checking.yml b/azure-pipelines-templates/type_checking.yml new file mode 100644 index 000000000..dbabd6570 --- /dev/null +++ b/azure-pipelines-templates/type_checking.yml @@ -0,0 +1,48 @@ +# Copyright (c) 2025, Shotgun Software Inc. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# - Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# - Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# - Neither the name of the Shotgun Software Inc nor the names of its +# contributors may be used to endorse or promote products derived from this +# software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +jobs: +- job: type_checking + displayName: Type Checking (beta) + pool: + vmImage: 'ubuntu-latest' + + steps: + - task: UsePythonVersion@0 + inputs: + versionSpec: 3.9 + addToPath: True + architecture: 'x64' + + - script: | + pip install --upgrade pip setuptools wheel + pip install --upgrade mypy + displayName: Install dependencies + + # Placeholder to future static type checking. For now we just run mypy and skip all known errors. + - bash: mypy shotgun_api3/shotgun.py --follow-imports skip --pretty --no-strict-optional --disable-error-code arg-type --disable-error-code assignment --disable-error-code return --disable-error-code return-value --disable-error-code attr-defined + displayName: Run type checking diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 52e6cfa9c..0e465bf22 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -52,6 +52,7 @@ pr: # Jobs run in parallel. jobs: - template: azure-pipelines-templates/code_style_validation.yml +- template: azure-pipelines-templates/type_checking.yml # These are jobs templates, they allow to reduce the redundancy between # variations of the same build. We pass in the image name diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index 2a1108aac..39167971c 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -29,6 +29,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ +from __future__ import annotations # Requried for 3.7 + import base64 import copy import datetime @@ -53,10 +55,11 @@ from typing import ( Any, BinaryIO, + Dict, Iterable, - Literal, + List, Optional, - TypedDict, + Tuple, TypeVar, Union, ) @@ -100,21 +103,25 @@ T = TypeVar("T") +if sys.version_info < (3, 9): + OrderItem = Dict + GroupingItem = Dict + BaseEntity = Dict +else: + from typing import TypedDict -class OrderItem(TypedDict): - field_name: str - direction: str - - -class GroupingItem(TypedDict): - field: str - type: str - direction: str + class OrderItem(TypedDict): + field_name: str + direction: str + class GroupingItem(TypedDict): + field: str + type: str + direction: str -class BaseEntity(TypedDict, total=False): - id: int - type: str + class BaseEntity(TypedDict, total=False): + id: int + type: str # ---------------------------------------------------------------------------- @@ -202,7 +209,7 @@ class ServerCapabilities(object): the future. Therefore, usage of this class is discouraged. """ - def __init__(self, host: str, meta: dict[str, Any]) -> None: + def __init__(self, host: str, meta: Dict[str, Any]) -> None: """ ServerCapabilities.__init__ @@ -249,7 +256,7 @@ def _ensure_python_version_supported(self) -> None: if sys.version_info < (3, 7): raise ShotgunError("This module requires Python version 3.7 or higher.") - def _ensure_support(self, feature: dict[str, Any], raise_hell: bool = True) -> bool: + def _ensure_support(self, feature: Dict[str, Any], raise_hell: bool = True) -> bool: """ Checks the server version supports a given feature, raises an exception if it does not. @@ -421,7 +428,7 @@ def __init__(self, sg: "Shotgun"): self.auth_token: Optional[str] = None self.sudo_as_login: Optional[str] = None # Authentication parameters to be folded into final auth_params dict - self.extra_auth_params: Optional[dict[str, Any]] = None + self.extra_auth_params: Optional[Dict[str, Any]] = None # uuid as a string self.session_uuid: Optional[str] = None self.scheme: Optional[str] = None @@ -738,7 +745,7 @@ def __init__( self.config.user_password = None self.config.auth_token = None - def _split_url(self, base_url: str) -> tuple[Optional[str], Optional[str]]: + def _split_url(self, base_url: str) -> Tuple[Optional[str], Optional[str]]: """ Extract the hostname:port and username/password/token from base_url sent when connect to the API. @@ -770,7 +777,7 @@ def _split_url(self, base_url: str) -> tuple[Optional[str], Optional[str]]: # API Functions @property - def server_info(self) -> dict[str, Any]: + def server_info(self) -> Dict[str, Any]: """ Property containing server information. @@ -823,7 +830,7 @@ def close(self) -> None: self._close_connection() return - def info(self) -> dict[str, Any]: + def info(self) -> Dict[str, Any]: """ Get API-related metadata from the Shotgun server. @@ -857,13 +864,13 @@ def info(self) -> dict[str, Any]: def find_one( self, entity_type: str, - filters: Union[list, tuple, dict[str, Any]], - fields: Optional[list[str]] = None, - order: Optional[list[OrderItem]] = None, - filter_operator: Optional[Literal["all", "any"]] = None, + filters: Union[List, Tuple, Dict[str, Any]], + fields: Optional[List[str]] = None, + order: Optional[List[OrderItem]] = None, + filter_operator: Optional[str] = None, retired_only: bool = False, include_archived_projects: bool = True, - additional_filter_presets: Optional[list[dict[str, Any]]] = None, + additional_filter_presets: Optional[List[Dict[str, Any]]] = None, ) -> Optional[BaseEntity]: """ Shortcut for :meth:`~shotgun_api3.Shotgun.find` with ``limit=1`` so it returns a single @@ -937,16 +944,16 @@ def find_one( def find( self, entity_type: str, - filters: Union[list, tuple, dict[str, Any]], - fields: Optional[list[str]] = None, - order: Optional[list[OrderItem]] = None, - filter_operator: Optional[Literal["all", "any"]] = None, + filters: Union[List, Tuple, Dict[str, Any]], + fields: Optional[List[str]] = None, + order: Optional[List[OrderItem]] = None, + filter_operator: Optional[str] = None, limit: int = 0, retired_only: bool = False, page: int = 0, include_archived_projects: bool = True, - additional_filter_presets: Optional[list[dict[str, Any]]] = None, - ) -> list[BaseEntity]: + additional_filter_presets: Optional[List[Dict[str, Any]]] = None, + ) -> List[BaseEntity]: """ Find entities matching the given filters. @@ -1136,14 +1143,14 @@ def find( def _construct_read_parameters( self, entity_type: str, - fields: Optional[list[str]], - filters: dict[str, Any], + fields: Optional[List[str]], + filters: Dict[str, Any], retired_only: bool, - order: Optional[list[dict[str, Any]]], + order: Optional[List[Dict[str, Any]]], include_archived_projects: bool, - additional_filter_presets: Optional[list[dict[str, Any]]], - ) -> dict[str, Any]: - params: dict[str, Any] = {} + additional_filter_presets: Optional[List[Dict[str, Any]]], + ) -> Dict[str, Any]: + params: Dict[str, Any] = {} params["type"] = entity_type params["return_fields"] = fields or ["id"] params["filters"] = filters @@ -1174,8 +1181,8 @@ def _construct_read_parameters( return params def _add_project_param( - self, params: dict[str, Any], project_entity - ) -> dict[str, Any]: + self, params: Dict[str, Any], project_entity + ) -> Dict[str, Any]: if project_entity and self.server_caps.ensure_per_project_customization(): params["project"] = project_entity @@ -1186,9 +1193,9 @@ def _translate_update_params( self, entity_type: str, entity_id: int, - data: dict, - multi_entity_update_modes: Optional[dict], - ) -> dict[str, Any]: + data: Dict, + multi_entity_update_modes: Optional[Dict], + ) -> Dict[str, Any]: global SHOTGUN_API_DISABLE_ENTITY_OPTIMIZATION def optimize_field(field_dict): @@ -1211,12 +1218,12 @@ def optimize_field(field_dict): def summarize( self, entity_type: str, - filters: Union[list, dict[str, Any]], - summary_fields: list[dict[str, str]], + filters: Union[List, Dict[str, Any]], + summary_fields: List[Dict[str, str]], filter_operator: Optional[str] = None, - grouping: Optional[list[GroupingItem]] = None, + grouping: Optional[List[GroupingItem]] = None, include_archived_projects: bool = True, - ) -> dict[str, Any]: + ) -> Dict[str, Any]: """ Summarize field data returned by a query. @@ -1419,9 +1426,9 @@ def summarize( def create( self, entity_type: str, - data: dict[str, Any], - return_fields: Optional[list] = None, - ) -> dict[str, Any]: + data: Dict[str, Any], + return_fields: Optional[List[str]] = None, + ) -> Dict[str, Any]: """ Create a new entity of the specified ``entity_type``. @@ -1508,8 +1515,8 @@ def update( self, entity_type: str, entity_id: int, - data: dict[str, Any], - multi_entity_update_modes: Optional[dict[str, Any]] = None, + data: Dict[str, Any], + multi_entity_update_modes: Optional[Dict[str, Any]] = None, ) -> BaseEntity: """ Update the specified entity with the supplied data. @@ -1631,7 +1638,7 @@ def revive(self, entity_type: str, entity_id: int) -> bool: return self._call_rpc("revive", params) - def batch(self, requests: list[dict[str, Any]]) -> list[dict[str, Any]]: + def batch(self, requests: List[Dict[str, Any]]) -> List[Dict[str, Any]]: """ Make a batch request of several :meth:`~shotgun_api3.Shotgun.create`, :meth:`~shotgun_api3.Shotgun.update`, and :meth:`~shotgun_api3.Shotgun.delete` calls. @@ -1750,9 +1757,9 @@ def work_schedule_read( self, start_date: str, end_date: str, - project: Optional[dict[str, Any]] = None, - user: Optional[dict[str, Any]] = None, - ) -> dict[str, Any]: + project: Optional[Dict[str, Any]] = None, + user: Optional[Dict[str, Any]] = None, + ) -> Dict[str, Any]: """ Return the work day rules for a given date range. @@ -1826,10 +1833,10 @@ def work_schedule_update( date: str, working: bool, description: Optional[str] = None, - project: Optional[dict[str, Any]] = None, - user: Optional[dict[str, Any]] = None, + project: Optional[Dict[str, Any]] = None, + user: Optional[Dict[str, Any]] = None, recalculate_field: Optional[str] = None, - ) -> dict[str, Any]: + ) -> Dict[str, Any]: """ Update the work schedule for a given date. @@ -1883,7 +1890,7 @@ def work_schedule_update( return self._call_rpc("work_schedule_update", params) - def follow(self, user: dict[str, Any], entity: dict[str, Any]) -> dict[str, Any]: + def follow(self, user: Dict[str, Any], entity: Dict[str, Any]) -> Dict[str, Any]: """ Add the entity to the user's followed entities. @@ -1911,7 +1918,7 @@ def follow(self, user: dict[str, Any], entity: dict[str, Any]) -> dict[str, Any] return self._call_rpc("follow", params) - def unfollow(self, user: dict[str, Any], entity: dict[str, Any]) -> dict[str, Any]: + def unfollow(self, user: Dict[str, Any], entity: Dict[str, Any]) -> Dict[str, Any]: """ Remove entity from the user's followed entities. @@ -1938,7 +1945,7 @@ def unfollow(self, user: dict[str, Any], entity: dict[str, Any]) -> dict[str, An return self._call_rpc("unfollow", params) - def followers(self, entity: dict[str, Any]) -> list[dict[str, Any]]: + def followers(self, entity: Dict[str, Any]) -> List[Dict[str, Any]]: """ Return all followers for an entity. @@ -1968,10 +1975,10 @@ def followers(self, entity: dict[str, Any]) -> list[dict[str, Any]]: def following( self, - user: dict[str, Any], - project: Optional[dict[str, Any]] = None, + user: Dict[str, Any], + project: Optional[Dict[str, Any]] = None, entity_type: Optional[str] = None, - ) -> list[BaseEntity]: + ) -> List[BaseEntity]: """ Return all entity instances a user is following. @@ -2004,7 +2011,7 @@ def following( def schema_entity_read( self, project_entity: Optional[BaseEntity] = None - ) -> dict[str, dict[str, Any]]: + ) -> Dict[str, Dict[str, Any]]: """ Return all active entity types, their display names, and their visibility. @@ -2039,7 +2046,7 @@ def schema_entity_read( The returned display names for this method will be localized when the ``localize`` Shotgun config property is set to ``True``. See :ref:`localization` for more information. """ - params: dict[str, Any] = {} + params: Dict[str, Any] = {} params = self._add_project_param(params, project_entity) @@ -2050,7 +2057,7 @@ def schema_entity_read( def schema_read( self, project_entity: Optional[BaseEntity] = None - ) -> dict[str, dict[str, Any]]: + ) -> Dict[str, Dict[str, Any]]: """ Get the schema for all fields on all entities. @@ -2113,7 +2120,7 @@ def schema_read( The returned display names for this method will be localized when the ``localize`` Shotgun config property is set to ``True``. See :ref:`localization` for more information. """ - params: dict[str, Any] = {} + params: Dict[str, Any] = {} params = self._add_project_param(params, project_entity) @@ -2127,7 +2134,7 @@ def schema_field_read( entity_type: str, field_name: Optional[str] = None, project_entity: Optional[BaseEntity] = None, - ) -> dict[str, dict[str, Any]]: + ) -> Dict[str, Dict[str, Any]]: """ Get schema for all fields on the specified entity type or just the field name specified if provided. @@ -2196,7 +2203,7 @@ def schema_field_create( entity_type: str, data_type: str, display_name: str, - properties: Optional[dict[str, Any]] = None, + properties: Optional[Dict[str, Any]] = None, ) -> str: """ Create a field for the specified entity type. @@ -2238,7 +2245,7 @@ def schema_field_update( self, entity_type: str, field_name: str, - properties: dict[str, Any], + properties: Dict[str, Any], project_entity: Optional[BaseEntity] = None, ) -> bool: """ @@ -2348,7 +2355,7 @@ def set_session_uuid(self, session_uuid: str) -> None: def share_thumbnail( self, - entities: list[dict[str, Any]], + entities: List[Dict[str, Any]], thumbnail_path: Optional[str] = None, source_entity: Optional[BaseEntity] = None, filmstrip_thumbnail: bool = False, @@ -2838,7 +2845,7 @@ def _upload_to_sg( def _get_attachment_upload_info( self, is_thumbnail: bool, filename: str, is_multipart_upload: bool - ) -> dict[str, Any]: + ) -> Dict[str, Any]: """ Internal function to get the information needed to upload a file to Cloud storage. @@ -2887,7 +2894,7 @@ def _get_attachment_upload_info( def download_attachment( self, - attachment: Union[dict[str, Any], Literal[False]] = False, + attachment: Union[Dict[str, Any], bool] = False, file_path: Optional[str] = None, attachment_id: Optional[int] = None, ) -> Union[str, bytes, None]: @@ -3100,7 +3107,7 @@ def get_attachment_download_url( def authenticate_human_user( self, user_login: str, user_password: str, auth_token: Optional[str] = None - ) -> Union[dict[str, Any], None]: + ) -> Union[Dict[str, Any], None]: """ Authenticate Shotgun HumanUser. @@ -3160,7 +3167,7 @@ def authenticate_human_user( raise def update_project_last_accessed( - self, project: dict[str, Any], user: Optional[dict[str, Any]] = None + self, project: Dict[str, Any], user: Optional[Dict[str, Any]] = None ) -> None: """ Update a Project's ``last_accessed_by_current_user`` field to the current timestamp. @@ -3208,8 +3215,8 @@ def update_project_last_accessed( self._parse_records(record)[0] def note_thread_read( - self, note_id: int, entity_fields: Optional[dict[str, Any]] = None - ) -> list[dict[str, Any]]: + self, note_id: int, entity_fields: Optional[Dict[str, Any]] = None + ) -> List[Dict[str, Any]]: """ Return the full conversation for a given note, including Replies and Attachments. @@ -3287,10 +3294,10 @@ def note_thread_read( def text_search( self, text: str, - entity_types: dict[str, Any], - project_ids: Optional[list] = None, + entity_types: Dict[str, Any], + project_ids: Optional[List] = None, limit: Optional[int] = None, - ) -> dict[str, Any]: + ) -> Dict[str, Any]: """ Search across the specified entity types for the given text. @@ -3386,11 +3393,11 @@ def activity_stream_read( self, entity_type: str, entity_id: int, - entity_fields: Optional[dict[str, Any]] = None, + entity_fields: Optional[Dict[str, Any]] = None, min_id: Optional[int] = None, max_id: Optional[int] = None, limit: Optional[int] = None, - ) -> dict[str, Any]: + ) -> Dict[str, Any]: """ Retrieve activity stream data from Shotgun. @@ -3524,8 +3531,8 @@ def nav_search_string( def nav_search_entity( self, root_path: str, - entity: dict[str, Any], - seed_entity_field: Optional[dict[str, Any]] = None, + entity: Dict[str, Any], + seed_entity_field: Optional[Dict[str, Any]] = None, ): """ Search function adapted to work with the navigation hierarchy. @@ -3570,7 +3577,7 @@ def get_session_token(self) -> str: return session_token - def preferences_read(self, prefs: Optional[list] = None) -> dict[str, Any]: + def preferences_read(self, prefs: Optional[List] = None) -> Dict[str, Any]: """ Get a subset of the site preferences. @@ -3593,7 +3600,7 @@ def preferences_read(self, prefs: Optional[list] = None) -> dict[str, Any]: return self._call_rpc("preferences_read", {"prefs": prefs}) - def user_subscriptions_read(self) -> list: + def user_subscriptions_read(self) -> List: """ Get the list of user subscriptions. @@ -3606,7 +3613,7 @@ def user_subscriptions_read(self) -> list: return self._call_rpc("user_subscriptions_read", None) def user_subscriptions_create( - self, users: list[dict[str, Union[str, list[str], None]]] + self, users: List[Dict[str, Union[str, List[str], None]]] ) -> bool: """ Assign subscriptions to users. @@ -3799,7 +3806,7 @@ def _call_rpc( return results[0] return results - def _auth_params(self) -> dict[str, Any]: + def _auth_params(self) -> Dict[str, Any]: """ Return a dictionary of the authentication parameters being used. """ @@ -3854,7 +3861,7 @@ def _auth_params(self) -> dict[str, Any]: return auth_params - def _sanitize_auth_params(self, params: dict[str, Any]) -> dict[str, Any]: + def _sanitize_auth_params(self, params: Dict[str, Any]) -> Dict[str, Any]: """ Given an authentication parameter dictionary, sanitize any sensitive information and return the sanitized dict copy. @@ -3867,7 +3874,7 @@ def _sanitize_auth_params(self, params: dict[str, Any]) -> dict[str, Any]: def _build_payload( self, method: str, params, include_auth_params: bool = True - ) -> dict[str, Any]: + ) -> Dict[str, Any]: """ Build the payload to be send to the rpc endpoint. """ @@ -3897,8 +3904,8 @@ def _encode_payload(self, payload) -> bytes: return json.dumps(payload, ensure_ascii=False).encode("utf-8") def _make_call( - self, verb: str, path: str, body, headers: Optional[dict[str, Any]] - ) -> tuple[tuple[int, str], dict[str, Any], str]: + self, verb: str, path: str, body, headers: Optional[Dict[str, Any]] + ) -> Tuple[Tuple[int, str], Dict[str, Any], str]: """ Make an HTTP call to the server. @@ -3949,8 +3956,8 @@ def _make_call( time.sleep(rpc_attempt_interval) def _http_request( - self, verb: str, path: str, body, headers: dict[str, Any] - ) -> tuple[tuple[int, str], dict[str, Any], str]: + self, verb: str, path: str, body, headers: Dict[str, Any] + ) -> Tuple[Tuple[int, str], Dict[str, Any], str]: """ Make the actual HTTP request. """ @@ -3988,7 +3995,7 @@ def _make_upload_request( raise return result - def _parse_http_status(self, status: tuple) -> None: + def _parse_http_status(self, status: Tuple) -> None: """ Parse the status returned from the http request. @@ -4007,8 +4014,8 @@ def _parse_http_status(self, status: tuple) -> None: return def _decode_response( - self, headers: dict[str, Any], body: str - ) -> Union[str, dict[str, Any]]: + self, headers: Dict[str, Any], body: str + ) -> Union[str, Dict[str, Any]]: """ Decode the response from the server from the wire format to a python data structure. @@ -4229,7 +4236,7 @@ def _close_connection(self) -> None: # ======================================================================== # Utility - def _parse_records(self, records: list) -> list: + def _parse_records(self, records: List) -> List: """ Parse 'records' returned from the api to do local modifications: @@ -4334,11 +4341,11 @@ def _build_thumb_url(self, entity_type: str, entity_id: int) -> str: def _dict_to_list( self, - d: Optional[dict[str, Any]], + d: Optional[Dict[str, Any]], key_name: str = "field_name", value_name: str = "value", extra_data=None, - ) -> list[dict[str, Any]]: + ) -> List[Dict[str, Any]]: """ Utility function to convert a dict into a list dicts using the key_name and value_name keys. @@ -4355,7 +4362,9 @@ def _dict_to_list( ret.append(d) return ret - def _dict_to_extra_data(self, d: Optional[dict], key_name="value") -> dict: + def _dict_to_extra_data( + self, d: Optional[Dict[str, Any]], key_name="value" + ) -> Dict[str, Any]: """ Utility function to convert a dict into a dict compatible with the extra_data arg of _dict_to_list. @@ -4385,7 +4394,7 @@ def _upload_file_to_storage(self, path: str, storage_url: str) -> None: LOG.debug("File uploaded to Cloud storage: %s", filename) def _multipart_upload_file_to_storage( - self, path: str, upload_info: dict[str, Any] + self, path: str, upload_info: Dict[str, Any] ) -> None: """ Internal function to upload a file to the Cloud storage in multiple parts. @@ -4429,7 +4438,7 @@ def _multipart_upload_file_to_storage( LOG.debug("File uploaded in multiple parts to Cloud storage: %s", path) def _get_upload_part_link( - self, upload_info: dict[str, Any], filename: str, part_number: int + self, upload_info: Dict[str, Any], filename: str, part_number: int ) -> str: """ Internal function to get the url to upload the next part of a file to the @@ -4528,7 +4537,7 @@ def _upload_data_to_storage( return etag def _complete_multipart_upload( - self, upload_info: dict[str, Any], filename: str, etags: Iterable[str] + self, upload_info: Dict[str, Any], filename: str, etags: Iterable[str] ) -> None: """ Internal function to complete a multi-part upload to the Cloud storage. @@ -4605,7 +4614,7 @@ def _requires_direct_s3_upload( else: return False - def _send_form(self, url: str, params: dict[str, Any]) -> str: + def _send_form(self, url: str, params: Dict[str, Any]) -> str: """ Utility function to send a Form to Shotgun and process any HTTP errors that could occur. @@ -4737,7 +4746,7 @@ def https_request(self, request): return self.http_request(request) -def _translate_filters(filters: Union[list, tuple], filter_operator) -> dict[str, Any]: +def _translate_filters(filters: Union[List, Tuple], filter_operator) -> Dict[str, Any]: """ Translate filters params into data structure expected by rpc call. """ @@ -4746,7 +4755,7 @@ def _translate_filters(filters: Union[list, tuple], filter_operator) -> dict[str return _translate_filters_dict(wrapped_filters) -def _translate_filters_dict(sg_filter: dict[str, Any]) -> dict[str, Any]: +def _translate_filters_dict(sg_filter: Dict[str, Any]) -> Dict[str, Any]: new_filters = {} filter_operator = sg_filter.get("filter_operator") @@ -4814,8 +4823,8 @@ def _version_str(version) -> str: def _optimize_filter_field( - field_value: Union[dict, list], recursive: bool = True -) -> Union[dict, list]: + field_value: Union[Dict[str, Any], List], recursive: bool = True +) -> Union[Dict, List]: """ For an FPT entity, returns a new dictionary with only the type, id, and other allowed keys. From a5675898758be0cca45896f544c32d502411b973 Mon Sep 17 00:00:00 2001 From: Julien Langlois <16244608+julien-lang@users.noreply.github.com> Date: Tue, 25 Nov 2025 12:40:03 -0800 Subject: [PATCH 117/125] SG-40994 Update deprecation warning message about end of compat with Py 3.7 (#424) * Update deprecation warning message about end of compat with Py 3.7 * Make module not importable * Address review and update documentation * Update shotgun_api3/__init__.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Better for review * Apply suggestions from code review Co-authored-by: Martin Chesnay <104032692+mchesnay@users.noreply.github.com> * Apply suggestion from review * Unify variable name with tk-core --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Martin Chesnay <104032692+mchesnay@users.noreply.github.com> --- docs/installation.rst | 12 ++++++++++-- docs/reference.rst | 13 +++++++++++++ shotgun_api3/__init__.py | 29 +++++++++++++++++++++++++---- shotgun_api3/shotgun.py | 8 -------- 4 files changed, 48 insertions(+), 14 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index b082b1669..fe64d2844 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -6,8 +6,6 @@ Installation Minimum Requirements ******************** -- Python 3.7 - .. note:: Some features of the API are only supported by more recent versions of the Flow Production Tracking server. These features are added to the Python API in a backwards compatible way so that existing @@ -15,6 +13,16 @@ Minimum Requirements your version of Flow Production Tracking will raise an appropriate exception. In general, we attempt to document these where possible. +Python versions +=============== + +The Python API library supports the following Python versions: `3.9 - 3.11`. We recommend using Python 3.11. + +.. important:: + Python versions older than 3.9 are no longer supported as of March 2025 and compatibility will be discontinued after + March 2026. + + ****************************** Installing into ``PYTHONPATH`` ****************************** diff --git a/docs/reference.rst b/docs/reference.rst index 28bfabf1b..ce2c92cc6 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -994,6 +994,19 @@ Will internally be transformed as if you invoked something like this: sg.find('Asset', [['project', 'is', {'id': 999, 'type': 'Project'}]]) +SHOTGUN_ALLOW_OLD_PYTHON +======================== + +When set to ``1``, ``shotgun_api3`` will allow being imported from Python versions that are no longer supported. +Otherwise, when unset (or set to any other value), importing the module will raise an exception. + +This is not recommended and should only be used for testing purposes. + +.. important:: + The ability to import the module does not guarantee that the module will work properly on the unsupported Python + version. In fact, it is very likely that it will not work properly. + + ************ Localization ************ diff --git a/shotgun_api3/__init__.py b/shotgun_api3/__init__.py index 943a9fa8b..553097a75 100644 --- a/shotgun_api3/__init__.py +++ b/shotgun_api3/__init__.py @@ -8,14 +8,35 @@ # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights # not expressly granted therein are reserved by Shotgun Software Inc. +import os import sys import warnings -if sys.version_info < (3, 9): +if sys.version_info < (3, 7): + 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.") + + 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 " + "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 since 2025-03 " - "and compatibility will be removed at any time after 2026-01. " - "Please update to Python 3.9 or a newer supported version.", + "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, ) diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index 39167971c..cd381e2ca 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -222,7 +222,6 @@ def __init__(self, host: str, meta: Dict[str, Any]) -> None: :ivar bool is_dev: ``True`` if server is running a development version of the Shotgun codebase. """ - self._ensure_python_version_supported() # Server host name self.host = host self.server_info = meta @@ -249,13 +248,6 @@ def __init__(self, host: str, meta: Dict[str, Any]) -> None: self.version = tuple(self.version[:3]) self._ensure_json_supported() - def _ensure_python_version_supported(self) -> None: - """ - Checks the if current Python version is supported. - """ - if sys.version_info < (3, 7): - raise ShotgunError("This module requires Python version 3.7 or higher.") - def _ensure_support(self, feature: Dict[str, Any], raise_hell: bool = True) -> bool: """ Checks the server version supports a given feature, raises an exception if it does not. From 24fa0e1d2354170b3e28d3370ba6e7e9df20b5d4 Mon Sep 17 00:00:00 2001 From: Carlos Villavicencio Date: Tue, 25 Nov 2025 17:34:03 -0500 Subject: [PATCH 118/125] Packaging for v3.9.1 (#428) --- HISTORY.rst | 10 ++++++++++ setup.py | 2 +- shotgun_api3/shotgun.py | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 7b9fc4018..cf9cd4304 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -4,6 +4,16 @@ Flow Production Tracking Python API Changelog Here you can see the full list of changes between each Python API release. +v3.9.1 (2025 Nov 25) +==================== + +- Removed the deprecated ``CACertsHTTPSConnection`` class, which was no longer needed after dropping Python 2 support. +- Added basic type annotations throughout the package to improve IDE support and code completion. Note: Some typing improvements are still in progress and will be refined in future releases. Special thanks to @chadrik for this contribution! +- Introduced a new environment variable ``SHOTGUN_ALLOW_OLD_PYTHON`` to temporarily bypass Python version warnings for users still on Python 3.7 or 3.8. While this provides flexibility during transition, we strongly recommend upgrading to Python 3.9 or newer for continued support and security updates. +- Enhanced payload optimization for entity dictionaries, making it more flexible and preventing potential issues when working with special fields like ``type`` and ``url``. +- Updated attachment documentation with detailed information about the ``relative_path`` field and its usage. +- Python versions older than 3.9 are now deprecated. A runtime warning will be displayed during initialization if you're using Python 3.7 or 3.8. Please plan to upgrade to Python 3.9 or newer as these older versions will not be supported in future releases. + v3.9.0 (2025 Sep 10) ==================== diff --git a/setup.py b/setup.py index 0ddda9d79..c0f416985 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ setup( name="shotgun_api3", - version="3.9.0", + version="3.9.1", description="Flow Production Tracking Python API", long_description=readme, author="Autodesk", diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index cd381e2ca..fb8803568 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -94,7 +94,7 @@ # ---------------------------------------------------------------------------- # Version -__version__ = "3.9.0" +__version__ = "3.9.1" # ---------------------------------------------------------------------------- From 89e083d4dbcaeccddb627eb9fcda77d9cbddaf6d Mon Sep 17 00:00:00 2001 From: Julien Langlois <16244608+julien-lang@users.noreply.github.com> Date: Tue, 9 Dec 2025 08:53:25 -0800 Subject: [PATCH 119/125] SG-41382 Switch Python version from 3.9 to 3.10 for running pre-commit (#430) --- azure-pipelines-templates/code_style_validation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines-templates/code_style_validation.yml b/azure-pipelines-templates/code_style_validation.yml index 69e82b7e0..9808f1d7a 100644 --- a/azure-pipelines-templates/code_style_validation.yml +++ b/azure-pipelines-templates/code_style_validation.yml @@ -34,7 +34,7 @@ jobs: steps: - task: UsePythonVersion@0 inputs: - versionSpec: 3.9 + versionSpec: 3.10 addToPath: True architecture: 'x64' From feddaa09eb38f76da7f03a5e26216db0f37dda4f Mon Sep 17 00:00:00 2001 From: kuldeepgudekar <49712593+kuldeepgudekar@users.noreply.github.com> Date: Wed, 10 Dec 2025 14:48:30 +0530 Subject: [PATCH 120/125] SG-35561 export csv python changes (#386) * changes to export page from the api * uncommented other test cases * doc string added in export_page function * args positioning changed * updated according to new export page params * update the doc string * removed extra line added * test cases updated * test case fixed * test case updated * update in test cases * update for human_user authmode * update in mock test cases * update in docs, and version number updated * update in doc string * update the documentation link * removing merge conflicts with update in documentation * update the test cases * release notes updated * small doc changes added * packaging for the v3.9.2 release * Revert "packaging for the v3.9.2 release" This reverts commit d359d054a525bde57f186a7ae8dc8efeb1cf8a68. * packaging for the v3.9.2 release * updated history for release info --- HISTORY.rst | 6 ++++ docs/reference.rst | 2 ++ setup.py | 2 +- shotgun_api3/shotgun.py | 25 ++++++++++++- tests/test_api.py | 77 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 110 insertions(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index cf9cd4304..b9d279bcb 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -4,6 +4,12 @@ Flow Production Tracking Python API Changelog Here you can see the full list of changes between each Python API release. +v3.9.2 (2025 Dec 10) +=================== + +- Add ``export_page`` method to Shotgun class. +- Documentation has been updated to reflect this addition. + v3.9.1 (2025 Nov 25) ==================== diff --git a/docs/reference.rst b/docs/reference.rst index ce2c92cc6..0c7f248f1 100644 --- a/docs/reference.rst +++ b/docs/reference.rst @@ -66,6 +66,7 @@ The documentation for all of the methods you'll need in your scripts lives in he Shotgun.work_schedule_read Shotgun.work_schedule_update Shotgun.preferences_read + Shotgun.export_page .. rubric:: Working With Files @@ -150,6 +151,7 @@ also some specialized convenience methods for accessing particular types of info .. automethod:: Shotgun.work_schedule_read .. automethod:: Shotgun.work_schedule_update .. automethod:: Shotgun.preferences_read +.. automethod:: Shotgun.export_page Working With Files ================== diff --git a/setup.py b/setup.py index c0f416985..19fd90115 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ setup( name="shotgun_api3", - version="3.9.1", + version="3.9.2", description="Flow Production Tracking Python API", long_description=readme, author="Autodesk", diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index fb8803568..9d2d22f26 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -94,7 +94,7 @@ # ---------------------------------------------------------------------------- # Version -__version__ = "3.9.1" +__version__ = "3.9.2" # ---------------------------------------------------------------------------- @@ -1882,6 +1882,29 @@ def work_schedule_update( return self._call_rpc("work_schedule_update", params) + def export_page(self, page_id, format, layout_name=None): + """ + Export the specified page to the given format. + This method allows you to export a page to CSV. + Respective layout or page should be marked as API Exportable in the Flow Production Tracking UI. + For more information, see documentation_. + .. _documentation: https://help.autodesk.com/view/SGSUB/ENU/?guid=SG_Tutorials_tu_export_csv_html#enable-api-export-for-a-page + If ``layout_name`` is not passed in, the default layout name will be used. + >>> sg.export_page(12345, "csv", layout_name="My Layout") + "ID,Name,Status\\n1,Shot 001,ip\\n2, Shot 002,rev\\n" + >>> sg.export_page(12345, "csv") + "ID,Name,Status\\n1,Shot 001,ip\\n2,Shot 002,rev\\n" + :param int page_id: The ID of the page to export. + :param str format: The format to export the page to. Supported format is ``"csv"``. + :param str layout_name: Optional layout name. This should be the name of the layout seen in the Flow Production Tracking UI. + :returns: string containing data of the given page. + :rtype: string + """ + + params = dict(format=format, page_id=page_id, layout_name=layout_name) + + return self._call_rpc("export_page", params) + def follow(self, user: Dict[str, Any], entity: Dict[str, Any]) -> Dict[str, Any]: """ Add the entity to the user's followed entities. diff --git a/tests/test_api.py b/tests/test_api.py index 19b738f60..d9a1a8691 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1947,6 +1947,83 @@ def test_include_archived_projects(self): self.sg.update("Project", self.project["id"], {"archived": False}) +class TestExportPage(base.LiveTestBase): + + def setUp(self): + super(TestExportPage, self).setUp("HumanUser") + + def test_export_page_unavailable(self): + """ + Test export_page raises when report does not exist. + """ + if not self.sg.server_caps.version or self.sg.server_caps.version < (5, 1, 22): + return + + page_entity = self.sg.create("Page", {"entity_type": "Shot"}) + + with self.assertRaises(Exception) as cm: + self.sg.export_page(page_entity["id"], "csv") + self.assertIn( + f"This functionality is currently not available", str(cm.exception) + ) + + with self.assertRaises(Exception) as cm: + self.sg.export_page(page_entity["id"], "csv", layout_name="My Layout") + self.assertIn( + f"This functionality is currently not available", str(cm.exception) + ) + + def test_export_page_format_missing(self): + """ + Test export_page raises for invalid format. + """ + if not self.sg.server_caps.version or self.sg.server_caps.version < (5, 1, 22): + return + + with self.assertRaises(Exception) as cm: + self.sg.export_page(11, None) + self.assertIn("'format' missing", str(cm.exception)) + + with self.assertRaises(Exception) as cm: + self.sg.export_page(11, None, layout_name="My Layout") + self.assertIn("'format' missing", str(cm.exception)) + + def test_export_page_missing_page_id(self): + """ + Test export_page raises for missing page id. + """ + if not self.sg.server_caps.version or self.sg.server_caps.version < (5, 1, 22): + return + + with self.assertRaises(Exception) as cm: + self.sg.export_page(None, "csv") + self.assertIn("'page_id' missing", str(cm.exception)) + + with self.assertRaises(Exception) as cm: + self.sg.export_page(None, "csv", layout_name="My Layout") + self.assertIn("'page_id' missing", str(cm.exception)) + + @unittest.mock.patch("shotgun_api3.shotgun.Http.request") + def test_export_page_without_layout_name(self, mock_request): + """ + Test export_page works when layout_name is not provided. + """ + + if not self.sg.server_caps.version or self.sg.server_caps.version < (5, 1, 22): + return + + # Mock the underlying Http.request to return CSV content with appropriate headers + csv_body = "ID,Name,Status\n1,Shot 001,ip\n2,Shot 002,rev\n" + response = unittest.mock.MagicMock(name="response mock") + response.status = 200 + response.reason = "OK" + response.items.return_value = [("content-type", "text/csv; charset=utf-8")] + mock_request.return_value = (response, csv_body) + result = self.sg.export_page(11, "csv") + self.assertIsInstance(result, str) + self.assertTrue(result.startswith("ID,Name,Status")) + + class TestFollow(base.LiveTestBase): def test_follow_unfollow(self): From d054f5d93e6c98c845335c9bb9d0160d50c54765 Mon Sep 17 00:00:00 2001 From: kuldeepgudekar <49712593+kuldeepgudekar@users.noreply.github.com> Date: Thu, 11 Dec 2025 13:49:32 +0530 Subject: [PATCH 121/125] Fixed Title underline in history rst file (#431) --- HISTORY.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HISTORY.rst b/HISTORY.rst index b9d279bcb..eea6dc053 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -5,7 +5,7 @@ Flow Production Tracking Python API Changelog Here you can see the full list of changes between each Python API release. v3.9.2 (2025 Dec 10) -=================== +==================== - Add ``export_page`` method to Shotgun class. - Documentation has been updated to reflect this addition. From ac46ff1db00a990e92d5c19302b9e10389de78be Mon Sep 17 00:00:00 2001 From: kuldeepgudekar <49712593+kuldeepgudekar@users.noreply.github.com> Date: Thu, 11 Dec 2025 17:12:12 +0530 Subject: [PATCH 122/125] fixing the documentation link (#432) --- shotgun_api3/shotgun.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index 9d2d22f26..2d06823c8 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -1885,11 +1885,15 @@ def work_schedule_update( def export_page(self, page_id, format, layout_name=None): """ Export the specified page to the given format. + This method allows you to export a page to CSV. + Respective layout or page should be marked as API Exportable in the Flow Production Tracking UI. - For more information, see documentation_. - .. _documentation: https://help.autodesk.com/view/SGSUB/ENU/?guid=SG_Tutorials_tu_export_csv_html#enable-api-export-for-a-page + If ``layout_name`` is not passed in, the default layout name will be used. + + For more information, see `documentation `_ . + >>> sg.export_page(12345, "csv", layout_name="My Layout") "ID,Name,Status\\n1,Shot 001,ip\\n2, Shot 002,rev\\n" >>> sg.export_page(12345, "csv") From 24b459132fd09d6c4a167c05ffb325f85044de19 Mon Sep 17 00:00:00 2001 From: kuldeepgudekar <49712593+kuldeepgudekar@users.noreply.github.com> Date: Tue, 16 Dec 2025 00:57:31 +0530 Subject: [PATCH 123/125] SG-35561 Update the test case for permissions and availability (#437) --- tests/test_api.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tests/test_api.py b/tests/test_api.py index d9a1a8691..d0e8407ea 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1960,18 +1960,20 @@ def test_export_page_unavailable(self): return page_entity = self.sg.create("Page", {"entity_type": "Shot"}) + error_messages = [ + "This functionality is currently not available", + f"Export for Page id={page_entity['id']} not available", + ] with self.assertRaises(Exception) as cm: self.sg.export_page(page_entity["id"], "csv") - self.assertIn( - f"This functionality is currently not available", str(cm.exception) - ) + msg = str(cm.exception) + self.assertIn(msg, error_messages) with self.assertRaises(Exception) as cm: self.sg.export_page(page_entity["id"], "csv", layout_name="My Layout") - self.assertIn( - f"This functionality is currently not available", str(cm.exception) - ) + msg = str(cm.exception) + self.assertIn(msg, error_messages) def test_export_page_format_missing(self): """ From 81ffb8be6f4da216bb150856706826fdc73bc462 Mon Sep 17 00:00:00 2001 From: Julien Langlois <16244608+julien-lang@users.noreply.github.com> Date: Tue, 16 Dec 2025 09:18:39 -0800 Subject: [PATCH 124/125] SG-41463 Fixup docs warning (#435) docstring of shotgun_api3.shotgun.Shotgun.export_page:15: Block quote ends without a blank line; unexpected unindent. --- shotgun_api3/shotgun.py | 1 + 1 file changed, 1 insertion(+) diff --git a/shotgun_api3/shotgun.py b/shotgun_api3/shotgun.py index 2d06823c8..9b6a91a9d 100644 --- a/shotgun_api3/shotgun.py +++ b/shotgun_api3/shotgun.py @@ -1898,6 +1898,7 @@ def export_page(self, page_id, format, layout_name=None): "ID,Name,Status\\n1,Shot 001,ip\\n2, Shot 002,rev\\n" >>> sg.export_page(12345, "csv") "ID,Name,Status\\n1,Shot 001,ip\\n2,Shot 002,rev\\n" + :param int page_id: The ID of the page to export. :param str format: The format to export the page to. Supported format is ``"csv"``. :param str layout_name: Optional layout name. This should be the name of the layout seen in the Flow Production Tracking UI. From e56d41c14f8efb6bddf8f24085e19007e71b9e26 Mon Sep 17 00:00:00 2001 From: Julien Langlois <16244608+julien-lang@users.noreply.github.com> Date: Tue, 16 Dec 2025 12:03:00 -0800 Subject: [PATCH 125/125] SG-41463 Improve CI Pipeline by re-using tk-ci-tools and build Sphinx documentation (#434) --- .../code_style_validation.yml | 50 ------------------- azure-pipelines.yml | 19 ++++++- 2 files changed, 18 insertions(+), 51 deletions(-) delete mode 100644 azure-pipelines-templates/code_style_validation.yml diff --git a/azure-pipelines-templates/code_style_validation.yml b/azure-pipelines-templates/code_style_validation.yml deleted file mode 100644 index 9808f1d7a..000000000 --- a/azure-pipelines-templates/code_style_validation.yml +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright (c) 2024, Shotgun Software Inc. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# - Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# - Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# - Neither the name of the Shotgun Software Inc nor the names of its -# contributors may be used to endorse or promote products derived from this -# software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -jobs: -- job: code_style_validation - displayName: Code Style Validation - pool: - vmImage: 'ubuntu-latest' - - steps: - - task: UsePythonVersion@0 - inputs: - versionSpec: 3.10 - addToPath: True - architecture: 'x64' - - - script: | - pip install --upgrade pip setuptools wheel - pip install --upgrade pre-commit - displayName: Install dependencies - - - bash: pre-commit autoupdate - displayName: Update pre-commit hook versions - - - bash: pre-commit run --all - displayName: Validate code with pre-commit diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 0e465bf22..b9be5ab7e 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -26,6 +26,16 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +resources: + repositories: + - repository: templates + type: github + name: shotgunsoftware/tk-ci-tools + ref: refs/heads/master + endpoint: shotgunsoftware + # Despite using the "tk-" prefix, tk-ci-tools is not a Toolkit only tool. + # We use it to avoid duplicating and maintaining CI pipeline code. + # We've stored some variables in Azure. They contain credentials # and are encrypted. They are also not available to clients. # This statement says which variable groups this repo requires. @@ -51,7 +61,14 @@ pr: # This here is the list of jobs we want to run for our build. # Jobs run in parallel. jobs: -- template: azure-pipelines-templates/code_style_validation.yml +- template: build-pipeline.yml@templates + parameters: + # Python API does not follow the exact same Python version lifecycle than + # Toolkit. So we prefer to control the test execution here instead. + has_unit_tests: false + + has_ui_resources: false + - template: azure-pipelines-templates/type_checking.yml # These are jobs templates, they allow to reduce the redundancy between