1# -------------------------------------------------------------------------
2# Copyright (c) Microsoft Corporation. All rights reserved.
3# Licensed under the MIT License. See License.txt in the project root for
4# license information.
5# --------------------------------------------------------------------------
6from sys import version_info
7
8if version_info < (3,):
9    def _str(value):
10        if isinstance(value, unicode):
11            return value.encode('utf-8')
12
13        return str(value)
14else:
15    _str = str
16
17
18def _to_str(value):
19    return _str(value) if value is not None else None
20
21
22from azure.common import (
23    AzureHttpError,
24    AzureConflictHttpError,
25    AzureMissingResourceHttpError,
26    AzureException,
27)
28from ._constants import (
29    _ENCRYPTION_PROTOCOL_V1,
30)
31
32_ERROR_CONFLICT = 'Conflict ({0})'
33_ERROR_NOT_FOUND = 'Not found ({0})'
34_ERROR_UNKNOWN = 'Unknown error ({0})'
35_ERROR_STORAGE_MISSING_INFO = \
36    'You need to provide an account name and either an account_key or sas_token when creating a storage service.'
37_ERROR_EMULATOR_DOES_NOT_SUPPORT_FILES = \
38    'The emulator does not support the file service.'
39_ERROR_ACCESS_POLICY = \
40    'share_access_policy must be either SignedIdentifier or AccessPolicy ' + \
41    'instance'
42_ERROR_PARALLEL_NOT_SEEKABLE = 'Parallel operations require a seekable stream.'
43_ERROR_VALUE_SHOULD_BE_BYTES = '{0} should be of type bytes.'
44_ERROR_VALUE_SHOULD_BE_BYTES_OR_STREAM = '{0} should be of type bytes or a readable file-like/io.IOBase stream object.'
45_ERROR_VALUE_SHOULD_BE_SEEKABLE_STREAM = '{0} should be a seekable file-like/io.IOBase type stream object.'
46_ERROR_VALUE_SHOULD_BE_STREAM = '{0} should be a file-like/io.IOBase type stream object with a read method.'
47_ERROR_VALUE_NONE = '{0} should not be None.'
48_ERROR_VALUE_NONE_OR_EMPTY = '{0} should not be None or empty.'
49_ERROR_VALUE_NEGATIVE = '{0} should not be negative.'
50_ERROR_START_END_NEEDED_FOR_MD5 = \
51    'Both end_range and start_range need to be specified ' + \
52    'for getting content MD5.'
53_ERROR_RANGE_TOO_LARGE_FOR_MD5 = \
54    'Getting content MD5 for a range greater than 4MB ' + \
55    'is not supported.'
56_ERROR_MD5_MISMATCH = \
57    'MD5 mismatch. Expected value is \'{0}\', computed value is \'{1}\'.'
58_ERROR_TOO_MANY_ACCESS_POLICIES = \
59    'Too many access policies provided. The server does not support setting more than 5 access policies on a single resource.'
60_ERROR_OBJECT_INVALID = \
61    '{0} does not define a complete interface. Value of {1} is either missing or invalid.'
62_ERROR_UNSUPPORTED_ENCRYPTION_VERSION = \
63    'Encryption version is not supported.'
64_ERROR_DECRYPTION_FAILURE = \
65    'Decryption failed'
66_ERROR_ENCRYPTION_REQUIRED = \
67    'Encryption required but no key was provided.'
68_ERROR_DECRYPTION_REQUIRED = \
69    'Decryption required but neither key nor resolver was provided.' + \
70    ' If you do not want to decypt, please do not set the require encryption flag.'
71_ERROR_INVALID_KID = \
72    'Provided or resolved key-encryption-key does not match the id of key used to encrypt.'
73_ERROR_UNSUPPORTED_ENCRYPTION_ALGORITHM = \
74    'Specified encryption algorithm is not supported.'
75_ERROR_UNSUPPORTED_METHOD_FOR_ENCRYPTION = 'The require_encryption flag is set, but encryption is not supported' + \
76                                           ' for this method.'
77_ERROR_UNKNOWN_KEY_WRAP_ALGORITHM = 'Unknown key wrap algorithm.'
78_ERROR_DATA_NOT_ENCRYPTED = 'Encryption required, but received data does not contain appropriate metatadata.' + \
79                            'Data was either not encrypted or metadata has been lost.'
80
81
82def _dont_fail_on_exist(error):
83    ''' don't throw exception if the resource exists.
84    This is called by create_* APIs with fail_on_exist=False'''
85    if isinstance(error, AzureConflictHttpError):
86        return False
87    else:
88        raise error
89
90
91def _dont_fail_not_exist(error):
92    ''' don't throw exception if the resource doesn't exist.
93    This is called by create_* APIs with fail_on_exist=False'''
94    if isinstance(error, AzureMissingResourceHttpError):
95        return False
96    else:
97        raise error
98
99
100def _http_error_handler(http_error):
101    ''' Simple error handler for azure.'''
102    message = str(http_error)
103    error_code = None
104
105    if 'x-ms-error-code' in http_error.respheader:
106        error_code = http_error.respheader['x-ms-error-code']
107        message += ' ErrorCode: ' + error_code
108
109    if http_error.respbody is not None:
110        message += '\n' + http_error.respbody.decode('utf-8-sig')
111
112    ex = AzureHttpError(message, http_error.status)
113    ex.error_code = error_code
114
115    raise ex
116
117
118def _validate_type_bytes(param_name, param):
119    if not isinstance(param, bytes):
120        raise TypeError(_ERROR_VALUE_SHOULD_BE_BYTES.format(param_name))
121
122
123def _validate_type_bytes_or_stream(param_name, param):
124    if not (isinstance(param, bytes) or hasattr(param, 'read')):
125        raise TypeError(_ERROR_VALUE_SHOULD_BE_BYTES_OR_STREAM.format(param_name))
126
127
128def _validate_not_none(param_name, param):
129    if param is None:
130        raise ValueError(_ERROR_VALUE_NONE.format(param_name))
131
132
133def _validate_content_match(server_md5, computed_md5):
134    if server_md5 != computed_md5:
135        raise AzureException(_ERROR_MD5_MISMATCH.format(server_md5, computed_md5))
136
137
138def _validate_access_policies(identifiers):
139    if identifiers and len(identifiers) > 5:
140        raise AzureException(_ERROR_TOO_MANY_ACCESS_POLICIES)
141
142
143def _validate_key_encryption_key_wrap(kek):
144    # Note that None is not callable and so will fail the second clause of each check.
145    if not hasattr(kek, 'wrap_key') or not callable(kek.wrap_key):
146        raise AttributeError(_ERROR_OBJECT_INVALID.format('key encryption key', 'wrap_key'))
147    if not hasattr(kek, 'get_kid') or not callable(kek.get_kid):
148        raise AttributeError(_ERROR_OBJECT_INVALID.format('key encryption key', 'get_kid'))
149    if not hasattr(kek, 'get_key_wrap_algorithm') or not callable(kek.get_key_wrap_algorithm):
150        raise AttributeError(_ERROR_OBJECT_INVALID.format('key encryption key', 'get_key_wrap_algorithm'))
151
152
153def _validate_key_encryption_key_unwrap(kek):
154    if not hasattr(kek, 'get_kid') or not callable(kek.get_kid):
155        raise AttributeError(_ERROR_OBJECT_INVALID.format('key encryption key', 'get_kid'))
156    if not hasattr(kek, 'unwrap_key') or not callable(kek.unwrap_key):
157        raise AttributeError(_ERROR_OBJECT_INVALID.format('key encryption key', 'unwrap_key'))
158
159
160def _validate_encryption_required(require_encryption, kek):
161    if require_encryption and (kek is None):
162        raise ValueError(_ERROR_ENCRYPTION_REQUIRED)
163
164
165def _validate_decryption_required(require_encryption, kek, resolver):
166    if (require_encryption and (kek is None) and
167            (resolver is None)):
168        raise ValueError(_ERROR_DECRYPTION_REQUIRED)
169
170
171def _validate_encryption_protocol_version(encryption_protocol):
172    if not (_ENCRYPTION_PROTOCOL_V1 == encryption_protocol):
173        raise ValueError(_ERROR_UNSUPPORTED_ENCRYPTION_VERSION)
174
175
176def _validate_kek_id(kid, resolved_id):
177    if not (kid == resolved_id):
178        raise ValueError(_ERROR_INVALID_KID)
179
180
181def _validate_encryption_unsupported(require_encryption, key_encryption_key):
182    if require_encryption or (key_encryption_key is not None):
183        raise ValueError(_ERROR_UNSUPPORTED_METHOD_FOR_ENCRYPTION)
184
185
186def _validate_user_delegation_key(user_delegation_key):
187    _validate_not_none('user_delegation_key.signed_oid', user_delegation_key.signed_oid)
188    _validate_not_none('user_delegation_key.signed_tid', user_delegation_key.signed_tid)
189    _validate_not_none('user_delegation_key.signed_start', user_delegation_key.signed_start)
190    _validate_not_none('user_delegation_key.signed_expiry', user_delegation_key.signed_expiry)
191    _validate_not_none('user_delegation_key.signed_version', user_delegation_key.signed_version)
192    _validate_not_none('user_delegation_key.signed_service', user_delegation_key.signed_service)
193    _validate_not_none('user_delegation_key.value', user_delegation_key.value)
194
195
196# wraps a given exception with the desired exception type
197def _wrap_exception(ex, desired_type):
198    msg = ""
199    if len(ex.args) > 0:
200        msg = ex.args[0]
201    if version_info >= (3,):
202        # Automatic chaining in Python 3 means we keep the trace
203        return desired_type(msg)
204    else:
205        # There isn't a good solution in 2 for keeping the stack trace
206        # in general, or that will not result in an error in 3
207        # However, we can keep the previous error type and message
208        # TODO: In the future we will log the trace
209        return desired_type('{}: {}'.format(ex.__class__.__name__, msg))
210
211
212class AzureSigningError(AzureException):
213    """
214    Represents a fatal error when attempting to sign a request.
215    In general, the cause of this exception is user error. For example, the given account key is not valid.
216    Please visit https://docs.microsoft.com/en-us/azure/storage/common/storage-create-storage-account for more info.
217    """
218    pass
219