kepconfig.ua_gateway.client
client exposes an API to allow modifications (add, delete, modify) to
UA Gateway plug-in client connection objects within the Kepware Configuration API.
Certificate store read, remove and trust functionality is also available for the
client connections.
1# ------------------------------------------------------------------------- 2# Copyright (c) 2023 PTC Inc. and/or all its affiliates. All rights reserved. 3# See License.txt in the project root for 4# license information. 5# -------------------------------------------------------------------------- 6 7r"""`client` exposes an API to allow modifications (add, delete, modify) to 8UA Gateway plug-in client connection objects within the Kepware Configuration API. 9Certificate store read, remove and trust functionality is also available for the 10client connections. 11""" 12 13from typing import Union 14 15from kepconfig.structures import KepServiceResponse 16from ..connection import server 17from ..error import KepError, KepHTTPError 18from ..ua_gateway.common import _INTER_TYPE, _change_cert_trust, _create_url_cert, _create_url_client, _delete_cert_truststore, _create_url_inst_cert 19 20CLIENT_INSTANCE_CERTIFICATE = "Client Instance Certificate" 21 22def get_ua_client_connection(server: server, ua_client_connection: str) -> dict: 23 '''Returns the properties of the UAG client connection object. 24 25 :param server: instance of the `server` class 26 :param ua_client_connection: name of ua_client_connection 27 28 :return: Dict of data for the client connection requested 29 30 :raises KepHTTPError: If urllib provides an HTTPError 31 :raises KepURLError: If urllib provides an URLError 32 ''' 33 r = server._config_get(server.url + _create_url_client(ua_client_connection)) 34 return r.payload 35 36def get_all_ua_client_connections(server: server, *, options: dict = None) -> dict: 37 '''Returns list of all UAG client connection objects and their properties. 38 39 :param server: instance of the `server` class 40 :param options: *(optional)* Dict of parameters to filter, sort or pagenate the list of client connections. Options are `filter`, 41 `sortOrder`, `sortProperty`, `pageNumber`, and `pageSize` 42 43 :return: List of data for all client connections in Kepware server UAG 44 45 :raises KepHTTPError: If urllib provides an HTTPError 46 :raises KepURLError: If urllib provides an URLError 47 ''' 48 r = server._config_get(server.url + _create_url_client()) 49 return r.payload 50 51def add_ua_client_connection(server: server, DATA: Union[dict, list]) -> Union[bool, list]: 52 '''Add a `"UAG client connection"` or multiple `"UAG client connection"` objects to Kepware. This allows you 53 to create a client connection with all needed properties. 54 55 Additionally it can be used to pass a list of UAG client connections to be added all at once. 56 57 :param server: instance of the `server` class 58 :param DATA: Dict of the connection or a list of connections 59 expected by Kepware Configuration API 60 61 :return: True - If a "HTTP 201 - Created" is received from Kepware server 62 :return: If a "HTTP 207 - Multi-Status" is received from Kepware with a list of dict error responses for all 63 connections added that failed. 64 65 :raises KepHTTPError: If urllib provides an HTTPError 66 :raises KepURLError: If urllib provides an URLError 67 ''' 68 r = server._config_add(server.url + _create_url_client(), DATA) 69 if r.code == 201: return True 70 elif r.code == 207: 71 errors = [] 72 for item in r.payload: 73 if item['code'] != 201: 74 errors.append(item) 75 return errors 76 else: 77 raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 78 79def modify_ua_client_connection(server: server, DATA: dict, *, ua_client_connection: str = None, force: bool = False) -> bool: 80 '''Modify a UAG client connection object and it's properties in Kepware. If a `"ua_client_connection"` is not provided as an input, 81 you need to identify the client connection in the *'common.ALLTYPES_NAME'* property field in `"DATA"`. It will 82 assume that is the client connection that is to be modified. 83 84 :param server: instance of the `server` class 85 :param DATA: Dict of the `ua_client_connection` properties to be modified 86 :param ua_client_connection: *(optional)* name of client connection to modify. Only needed if not existing in `"DATA"` 87 :param force: *(optional)* if True, will force the configuration update to the Kepware server 88 89 :return: True - If a "HTTP 200 - OK" is received from Kepware server 90 91 :raises KepHTTPError: If urllib provides an HTTPError 92 :raises KepURLError: If urllib provides an URLError 93 ''' 94 client_data = server._force_update_check(force, DATA) 95 if ua_client_connection == None: 96 try: 97 r = server._config_update(server.url + _create_url_client(client_data['common.ALLTYPES_NAME']), client_data) 98 if r.code == 200: return True 99 else: raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 100 except KeyError as err: 101 err_msg = 'Error: No Channel identified in DATA | Key Error: {}'.format(err) 102 raise KepError(err_msg) 103 # except Exception as e: 104 # return 'Error: Error with {}: {}'.format(inspect.currentframe().f_code.co_name, str(e)) 105 else: 106 r = server._config_update(server.url + _create_url_client(ua_client_connection), client_data) 107 if r.code == 200: return True 108 else: 109 raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 110 111def del_ua_client_connection(server: server, ua_client_connection: str) -> bool: 112 '''Delete a `"UAG client connection"` object in Kepware. 113 114 :param server: instance of the `server` class 115 :param ua_client_connection: name of client connection 116 117 :return: True - If a "HTTP 200 - OK" is received from Kepware server 118 119 :raises KepHTTPError: If urllib provides an HTTPError 120 :raises KepURLError: If urllib provides an URLError 121 ''' 122 r = server._config_del(server.url + _create_url_client(ua_client_connection)) 123 if r.code == 200: return True 124 else: 125 raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 126 127def get_certificate(server: server, certificate: str) -> dict: 128 '''Returns the properties of the UAG client connection certificate object in the UAG client connection 129 certificate store. These are UA server instance certificates that are used by UAG client connections for 130 trust purposes in the UA security model. 131 132 :param server: instance of the `server` class 133 :param certificate: name of certificate 134 135 :return: Dict of data for the certificate requested 136 137 :raises KepHTTPError: If urllib provides an HTTPError 138 :raises KepURLError: If urllib provides an URLError 139 ''' 140 r = server._config_get(server.url + _create_url_cert(_INTER_TYPE.CLIENT, certificate)) 141 return r.payload 142 143def get_all_certificates(server: server, *, options: dict = None) -> list: 144 '''Returns list of all UAG client connection certificate objects and their properties. These are UA server instance 145 certificates that are used by UAG client connections for trust purposes in the UA security model. 146 147 :param server: instance of the `server` class 148 :param options: *(optional)* Dict of parameters to filter, sort or pagenate the list of certificates. Options are `filter`, 149 `sortOrder`, `sortProperty`, `pageNumber`, and `pageSize` 150 151 :return: List of data for all certificates in Kepware server UAG client connection certificate store 152 153 :raises KepHTTPError: If urllib provides an HTTPError 154 :raises KepURLError: If urllib provides an URLError 155 ''' 156 r = server._config_get(server.url + _create_url_cert(_INTER_TYPE.CLIENT), params= options) 157 return r.payload 158 159def trust_certificate(server: server, certificate: str) -> bool: 160 '''Trusts the certificate in the UAG client connection certifcate store. This is updating the trust state of UA server instance 161 certificates that are used by UAG client connections for trust purposes in the UA security model. 162 163 :param server: instance of the `server` class 164 :param certificate: name of certificate 165 166 :return: True - If a "HTTP 200 - OK" is received from Kepware server 167 168 :raises KepHTTPError: If urllib provides an HTTPError 169 :raises KepURLError: If urllib provides an URLError 170 ''' 171 return _change_cert_trust(server, _INTER_TYPE.CLIENT, certificate, True) 172 173def reject_certificate(server: server, certificate: str) -> bool: 174 '''Rejects the certificate in the UAG client connection certifcate store. This is updating the trust state of UA server instance 175 certificates that are used by UAG client connections for trust purposes in the UA security model. 176 177 :param server: instance of the `server` class 178 :param certificate: name of certificate 179 180 :return: True - If a "HTTP 200 - OK" is received from Kepware server 181 182 :raises KepHTTPError: If urllib provides an HTTPError 183 :raises KepURLError: If urllib provides an URLError 184 ''' 185 186 return _change_cert_trust(server, _INTER_TYPE.CLIENT, certificate, False) 187 188def delete_certificate(server: server, certificate: str) -> bool: 189 '''Deletes the certificate in the UAG client endpoint certificate store. 190 191 :param server: instance of the `server` class 192 :param certificate: name of certificate 193 194 :return: True - If a "HTTP 200 - OK" is received from Kepware server 195 196 :raises KepHTTPError: If urllib provides an HTTPError 197 :raises KepURLError: If urllib provides an URLError 198 ''' 199 return _delete_cert_truststore(server, _INTER_TYPE.CLIENT, certificate) 200 201def get_instance_certificate(server: server) -> dict: 202 '''Returns the properties of the UAG client instance certificate object in the UAG certificate store. 203 These are UAG instance certificates that are used by UAG for trust purposes in the UA security model. 204 205 :param server: instance of the `server` class 206 207 :return: Dict of properties for the certificate requested 208 209 :raises KepHTTPError: If urllib provides an HTTPError 210 :raises KepURLError: If urllib provides an URLError 211 ''' 212 r = server._config_get(server.url + _create_url_inst_cert(_INTER_TYPE.CLIENT, CLIENT_INSTANCE_CERTIFICATE)) 213 return r.payload 214 215def reissue_self_signed_instance_certificate(server: server, job_ttl: int = None) -> KepServiceResponse: 216 '''Deletes and reissues a self-signed UAG server instance certificate object in the UAG certificate store. 217 This is the UAG instance certificate that are used by UAG for trust purposes in the UA security model. 218 219 :param server: instance of the `server` class 220 :param job_ttl: *(optional)* Determines the number of seconds a job instance will exist following completion. 221 222 :return: `KepServiceResponse` instance with job information 223 224 :raises KepHTTPError: If urllib provides an HTTPError 225 :raises KepURLError: If urllib provides an URLError 226 ''' 227 url = server.url + _create_url_inst_cert(_INTER_TYPE.CLIENT, CLIENT_INSTANCE_CERTIFICATE) + '/services/ReIssueInstanceCertificate' 228 try: 229 job = server._kep_service_execute(url, TTL= job_ttl) 230 return job 231 except Exception as err: 232 raise err
23def get_ua_client_connection(server: server, ua_client_connection: str) -> dict: 24 '''Returns the properties of the UAG client connection object. 25 26 :param server: instance of the `server` class 27 :param ua_client_connection: name of ua_client_connection 28 29 :return: Dict of data for the client connection requested 30 31 :raises KepHTTPError: If urllib provides an HTTPError 32 :raises KepURLError: If urllib provides an URLError 33 ''' 34 r = server._config_get(server.url + _create_url_client(ua_client_connection)) 35 return r.payload
Returns the properties of the UAG client connection object.
Parameters
- server: instance of the
serverclass - ua_client_connection: name of ua_client_connection
Returns
Dict of data for the client connection requested
Raises
- KepHTTPError: If urllib provides an HTTPError
- KepURLError: If urllib provides an URLError
37def get_all_ua_client_connections(server: server, *, options: dict = None) -> dict: 38 '''Returns list of all UAG client connection objects and their properties. 39 40 :param server: instance of the `server` class 41 :param options: *(optional)* Dict of parameters to filter, sort or pagenate the list of client connections. Options are `filter`, 42 `sortOrder`, `sortProperty`, `pageNumber`, and `pageSize` 43 44 :return: List of data for all client connections in Kepware server UAG 45 46 :raises KepHTTPError: If urllib provides an HTTPError 47 :raises KepURLError: If urllib provides an URLError 48 ''' 49 r = server._config_get(server.url + _create_url_client()) 50 return r.payload
Returns list of all UAG client connection objects and their properties.
Parameters
- server: instance of the
serverclass - options: (optional) Dict of parameters to filter, sort or pagenate the list of client connections. Options are
filter,sortOrder,sortProperty,pageNumber, andpageSize
Returns
List of data for all client connections in Kepware server UAG
Raises
- KepHTTPError: If urllib provides an HTTPError
- KepURLError: If urllib provides an URLError
52def add_ua_client_connection(server: server, DATA: Union[dict, list]) -> Union[bool, list]: 53 '''Add a `"UAG client connection"` or multiple `"UAG client connection"` objects to Kepware. This allows you 54 to create a client connection with all needed properties. 55 56 Additionally it can be used to pass a list of UAG client connections to be added all at once. 57 58 :param server: instance of the `server` class 59 :param DATA: Dict of the connection or a list of connections 60 expected by Kepware Configuration API 61 62 :return: True - If a "HTTP 201 - Created" is received from Kepware server 63 :return: If a "HTTP 207 - Multi-Status" is received from Kepware with a list of dict error responses for all 64 connections added that failed. 65 66 :raises KepHTTPError: If urllib provides an HTTPError 67 :raises KepURLError: If urllib provides an URLError 68 ''' 69 r = server._config_add(server.url + _create_url_client(), DATA) 70 if r.code == 201: return True 71 elif r.code == 207: 72 errors = [] 73 for item in r.payload: 74 if item['code'] != 201: 75 errors.append(item) 76 return errors 77 else: 78 raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload)
Add a "UAG client connection" or multiple "UAG client connection" objects to Kepware. This allows you
to create a client connection with all needed properties.
Additionally it can be used to pass a list of UAG client connections to be added all at once.
Parameters
- server: instance of the
serverclass - DATA: Dict of the connection or a list of connections expected by Kepware Configuration API
Returns
True - If a "HTTP 201 - Created" is received from Kepware server
Returns
If a "HTTP 207 - Multi-Status" is received from Kepware with a list of dict error responses for all connections added that failed.
Raises
- KepHTTPError: If urllib provides an HTTPError
- KepURLError: If urllib provides an URLError
80def modify_ua_client_connection(server: server, DATA: dict, *, ua_client_connection: str = None, force: bool = False) -> bool: 81 '''Modify a UAG client connection object and it's properties in Kepware. If a `"ua_client_connection"` is not provided as an input, 82 you need to identify the client connection in the *'common.ALLTYPES_NAME'* property field in `"DATA"`. It will 83 assume that is the client connection that is to be modified. 84 85 :param server: instance of the `server` class 86 :param DATA: Dict of the `ua_client_connection` properties to be modified 87 :param ua_client_connection: *(optional)* name of client connection to modify. Only needed if not existing in `"DATA"` 88 :param force: *(optional)* if True, will force the configuration update to the Kepware server 89 90 :return: True - If a "HTTP 200 - OK" is received from Kepware server 91 92 :raises KepHTTPError: If urllib provides an HTTPError 93 :raises KepURLError: If urllib provides an URLError 94 ''' 95 client_data = server._force_update_check(force, DATA) 96 if ua_client_connection == None: 97 try: 98 r = server._config_update(server.url + _create_url_client(client_data['common.ALLTYPES_NAME']), client_data) 99 if r.code == 200: return True 100 else: raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload) 101 except KeyError as err: 102 err_msg = 'Error: No Channel identified in DATA | Key Error: {}'.format(err) 103 raise KepError(err_msg) 104 # except Exception as e: 105 # return 'Error: Error with {}: {}'.format(inspect.currentframe().f_code.co_name, str(e)) 106 else: 107 r = server._config_update(server.url + _create_url_client(ua_client_connection), client_data) 108 if r.code == 200: return True 109 else: 110 raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload)
Modify a UAG client connection object and it's properties in Kepware. If a "ua_client_connection" is not provided as an input,
you need to identify the client connection in the 'common.ALLTYPES_NAME' property field in "DATA". It will
assume that is the client connection that is to be modified.
Parameters
- server: instance of the
serverclass - DATA: Dict of the
ua_client_connectionproperties to be modified - ua_client_connection: (optional) name of client connection to modify. Only needed if not existing in
"DATA" - force: (optional) if True, will force the configuration update to the Kepware server
Returns
True - If a "HTTP 200 - OK" is received from Kepware server
Raises
- KepHTTPError: If urllib provides an HTTPError
- KepURLError: If urllib provides an URLError
112def del_ua_client_connection(server: server, ua_client_connection: str) -> bool: 113 '''Delete a `"UAG client connection"` object in Kepware. 114 115 :param server: instance of the `server` class 116 :param ua_client_connection: name of client connection 117 118 :return: True - If a "HTTP 200 - OK" is received from Kepware server 119 120 :raises KepHTTPError: If urllib provides an HTTPError 121 :raises KepURLError: If urllib provides an URLError 122 ''' 123 r = server._config_del(server.url + _create_url_client(ua_client_connection)) 124 if r.code == 200: return True 125 else: 126 raise KepHTTPError(r.url, r.code, r.msg, r.hdrs, r.payload)
Delete a "UAG client connection" object in Kepware.
Parameters
- server: instance of the
serverclass - ua_client_connection: name of client connection
Returns
True - If a "HTTP 200 - OK" is received from Kepware server
Raises
- KepHTTPError: If urllib provides an HTTPError
- KepURLError: If urllib provides an URLError
128def get_certificate(server: server, certificate: str) -> dict: 129 '''Returns the properties of the UAG client connection certificate object in the UAG client connection 130 certificate store. These are UA server instance certificates that are used by UAG client connections for 131 trust purposes in the UA security model. 132 133 :param server: instance of the `server` class 134 :param certificate: name of certificate 135 136 :return: Dict of data for the certificate requested 137 138 :raises KepHTTPError: If urllib provides an HTTPError 139 :raises KepURLError: If urllib provides an URLError 140 ''' 141 r = server._config_get(server.url + _create_url_cert(_INTER_TYPE.CLIENT, certificate)) 142 return r.payload
Returns the properties of the UAG client connection certificate object in the UAG client connection certificate store. These are UA server instance certificates that are used by UAG client connections for trust purposes in the UA security model.
Parameters
- server: instance of the
serverclass - certificate: name of certificate
Returns
Dict of data for the certificate requested
Raises
- KepHTTPError: If urllib provides an HTTPError
- KepURLError: If urllib provides an URLError
144def get_all_certificates(server: server, *, options: dict = None) -> list: 145 '''Returns list of all UAG client connection certificate objects and their properties. These are UA server instance 146 certificates that are used by UAG client connections for trust purposes in the UA security model. 147 148 :param server: instance of the `server` class 149 :param options: *(optional)* Dict of parameters to filter, sort or pagenate the list of certificates. Options are `filter`, 150 `sortOrder`, `sortProperty`, `pageNumber`, and `pageSize` 151 152 :return: List of data for all certificates in Kepware server UAG client connection certificate store 153 154 :raises KepHTTPError: If urllib provides an HTTPError 155 :raises KepURLError: If urllib provides an URLError 156 ''' 157 r = server._config_get(server.url + _create_url_cert(_INTER_TYPE.CLIENT), params= options) 158 return r.payload
Returns list of all UAG client connection certificate objects and their properties. These are UA server instance certificates that are used by UAG client connections for trust purposes in the UA security model.
Parameters
- server: instance of the
serverclass - options: (optional) Dict of parameters to filter, sort or pagenate the list of certificates. Options are
filter,sortOrder,sortProperty,pageNumber, andpageSize
Returns
List of data for all certificates in Kepware server UAG client connection certificate store
Raises
- KepHTTPError: If urllib provides an HTTPError
- KepURLError: If urllib provides an URLError
160def trust_certificate(server: server, certificate: str) -> bool: 161 '''Trusts the certificate in the UAG client connection certifcate store. This is updating the trust state of UA server instance 162 certificates that are used by UAG client connections for trust purposes in the UA security model. 163 164 :param server: instance of the `server` class 165 :param certificate: name of certificate 166 167 :return: True - If a "HTTP 200 - OK" is received from Kepware server 168 169 :raises KepHTTPError: If urllib provides an HTTPError 170 :raises KepURLError: If urllib provides an URLError 171 ''' 172 return _change_cert_trust(server, _INTER_TYPE.CLIENT, certificate, True)
Trusts the certificate in the UAG client connection certifcate store. This is updating the trust state of UA server instance certificates that are used by UAG client connections for trust purposes in the UA security model.
Parameters
- server: instance of the
serverclass - certificate: name of certificate
Returns
True - If a "HTTP 200 - OK" is received from Kepware server
Raises
- KepHTTPError: If urllib provides an HTTPError
- KepURLError: If urllib provides an URLError
174def reject_certificate(server: server, certificate: str) -> bool: 175 '''Rejects the certificate in the UAG client connection certifcate store. This is updating the trust state of UA server instance 176 certificates that are used by UAG client connections for trust purposes in the UA security model. 177 178 :param server: instance of the `server` class 179 :param certificate: name of certificate 180 181 :return: True - If a "HTTP 200 - OK" is received from Kepware server 182 183 :raises KepHTTPError: If urllib provides an HTTPError 184 :raises KepURLError: If urllib provides an URLError 185 ''' 186 187 return _change_cert_trust(server, _INTER_TYPE.CLIENT, certificate, False)
Rejects the certificate in the UAG client connection certifcate store. This is updating the trust state of UA server instance certificates that are used by UAG client connections for trust purposes in the UA security model.
Parameters
- server: instance of the
serverclass - certificate: name of certificate
Returns
True - If a "HTTP 200 - OK" is received from Kepware server
Raises
- KepHTTPError: If urllib provides an HTTPError
- KepURLError: If urllib provides an URLError
189def delete_certificate(server: server, certificate: str) -> bool: 190 '''Deletes the certificate in the UAG client endpoint certificate store. 191 192 :param server: instance of the `server` class 193 :param certificate: name of certificate 194 195 :return: True - If a "HTTP 200 - OK" is received from Kepware server 196 197 :raises KepHTTPError: If urllib provides an HTTPError 198 :raises KepURLError: If urllib provides an URLError 199 ''' 200 return _delete_cert_truststore(server, _INTER_TYPE.CLIENT, certificate)
Deletes the certificate in the UAG client endpoint certificate store.
Parameters
- server: instance of the
serverclass - certificate: name of certificate
Returns
True - If a "HTTP 200 - OK" is received from Kepware server
Raises
- KepHTTPError: If urllib provides an HTTPError
- KepURLError: If urllib provides an URLError
202def get_instance_certificate(server: server) -> dict: 203 '''Returns the properties of the UAG client instance certificate object in the UAG certificate store. 204 These are UAG instance certificates that are used by UAG for trust purposes in the UA security model. 205 206 :param server: instance of the `server` class 207 208 :return: Dict of properties for the certificate requested 209 210 :raises KepHTTPError: If urllib provides an HTTPError 211 :raises KepURLError: If urllib provides an URLError 212 ''' 213 r = server._config_get(server.url + _create_url_inst_cert(_INTER_TYPE.CLIENT, CLIENT_INSTANCE_CERTIFICATE)) 214 return r.payload
Returns the properties of the UAG client instance certificate object in the UAG certificate store. These are UAG instance certificates that are used by UAG for trust purposes in the UA security model.
Parameters
- server: instance of the
serverclass
Returns
Dict of properties for the certificate requested
Raises
- KepHTTPError: If urllib provides an HTTPError
- KepURLError: If urllib provides an URLError
216def reissue_self_signed_instance_certificate(server: server, job_ttl: int = None) -> KepServiceResponse: 217 '''Deletes and reissues a self-signed UAG server instance certificate object in the UAG certificate store. 218 This is the UAG instance certificate that are used by UAG for trust purposes in the UA security model. 219 220 :param server: instance of the `server` class 221 :param job_ttl: *(optional)* Determines the number of seconds a job instance will exist following completion. 222 223 :return: `KepServiceResponse` instance with job information 224 225 :raises KepHTTPError: If urllib provides an HTTPError 226 :raises KepURLError: If urllib provides an URLError 227 ''' 228 url = server.url + _create_url_inst_cert(_INTER_TYPE.CLIENT, CLIENT_INSTANCE_CERTIFICATE) + '/services/ReIssueInstanceCertificate' 229 try: 230 job = server._kep_service_execute(url, TTL= job_ttl) 231 return job 232 except Exception as err: 233 raise err
Deletes and reissues a self-signed UAG server instance certificate object in the UAG certificate store. This is the UAG instance certificate that are used by UAG for trust purposes in the UA security model.
Parameters
- server: instance of the
serverclass - job_ttl: (optional) Determines the number of seconds a job instance will exist following completion.
Returns
KepServiceResponseinstance with job information
Raises
- KepHTTPError: If urllib provides an HTTPError
- KepURLError: If urllib provides an URLError