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# --------------------------------------------------------------------------
6
7from datetime import date
8
9from .parser import _str, _to_utc_datetime
10from .constants import X_MS_VERSION
11from . import sign_string, url_quote
12
13
14class QueryStringConstants(object):
15    SIGNED_SIGNATURE = 'sig'
16    SIGNED_PERMISSION = 'sp'
17    SIGNED_START = 'st'
18    SIGNED_EXPIRY = 'se'
19    SIGNED_RESOURCE = 'sr'
20    SIGNED_IDENTIFIER = 'si'
21    SIGNED_IP = 'sip'
22    SIGNED_PROTOCOL = 'spr'
23    SIGNED_VERSION = 'sv'
24    SIGNED_CACHE_CONTROL = 'rscc'
25    SIGNED_CONTENT_DISPOSITION = 'rscd'
26    SIGNED_CONTENT_ENCODING = 'rsce'
27    SIGNED_CONTENT_LANGUAGE = 'rscl'
28    SIGNED_CONTENT_TYPE = 'rsct'
29    START_PK = 'spk'
30    START_RK = 'srk'
31    END_PK = 'epk'
32    END_RK = 'erk'
33    SIGNED_RESOURCE_TYPES = 'srt'
34    SIGNED_SERVICES = 'ss'
35    SIGNED_OID = 'skoid'
36    SIGNED_TID = 'sktid'
37    SIGNED_KEY_START = 'skt'
38    SIGNED_KEY_EXPIRY = 'ske'
39    SIGNED_KEY_SERVICE = 'sks'
40    SIGNED_KEY_VERSION = 'skv'
41
42    @staticmethod
43    def to_list():
44        return [
45            QueryStringConstants.SIGNED_SIGNATURE,
46            QueryStringConstants.SIGNED_PERMISSION,
47            QueryStringConstants.SIGNED_START,
48            QueryStringConstants.SIGNED_EXPIRY,
49            QueryStringConstants.SIGNED_RESOURCE,
50            QueryStringConstants.SIGNED_IDENTIFIER,
51            QueryStringConstants.SIGNED_IP,
52            QueryStringConstants.SIGNED_PROTOCOL,
53            QueryStringConstants.SIGNED_VERSION,
54            QueryStringConstants.SIGNED_CACHE_CONTROL,
55            QueryStringConstants.SIGNED_CONTENT_DISPOSITION,
56            QueryStringConstants.SIGNED_CONTENT_ENCODING,
57            QueryStringConstants.SIGNED_CONTENT_LANGUAGE,
58            QueryStringConstants.SIGNED_CONTENT_TYPE,
59            QueryStringConstants.START_PK,
60            QueryStringConstants.START_RK,
61            QueryStringConstants.END_PK,
62            QueryStringConstants.END_RK,
63            QueryStringConstants.SIGNED_RESOURCE_TYPES,
64            QueryStringConstants.SIGNED_SERVICES,
65            QueryStringConstants.SIGNED_OID,
66            QueryStringConstants.SIGNED_TID,
67            QueryStringConstants.SIGNED_KEY_START,
68            QueryStringConstants.SIGNED_KEY_EXPIRY,
69            QueryStringConstants.SIGNED_KEY_SERVICE,
70            QueryStringConstants.SIGNED_KEY_VERSION,
71        ]
72
73
74class SharedAccessSignature(object):
75    '''
76    Provides a factory for creating account access
77    signature tokens with an account name and account key. Users can either
78    use the factory or can construct the appropriate service and use the
79    generate_*_shared_access_signature method directly.
80    '''
81
82    def __init__(self, account_name, account_key, x_ms_version=X_MS_VERSION):
83        '''
84        :param str account_name:
85            The storage account name used to generate the shared access signatures.
86        :param str account_key:
87            The access key to generate the shares access signatures.
88        :param str x_ms_version:
89            The service version used to generate the shared access signatures.
90        '''
91        self.account_name = account_name
92        self.account_key = account_key
93        self.x_ms_version = x_ms_version
94
95    def generate_account(self, services, resource_types, permission, expiry, start=None,
96                         ip=None, protocol=None):
97        '''
98        Generates a shared access signature for the account.
99        Use the returned signature with the sas_token parameter of the service
100        or to create a new account object.
101
102        :param ResourceTypes resource_types:
103            Specifies the resource types that are accessible with the account
104            SAS. You can combine values to provide access to more than one
105            resource type.
106        :param AccountSasPermissions permission:
107            The permissions associated with the shared access signature. The
108            user is restricted to operations allowed by the permissions.
109            Required unless an id is given referencing a stored access policy
110            which contains this field. This field must be omitted if it has been
111            specified in an associated stored access policy. You can combine
112            values to provide more than one permission.
113        :param expiry:
114            The time at which the shared access signature becomes invalid.
115            Required unless an id is given referencing a stored access policy
116            which contains this field. This field must be omitted if it has
117            been specified in an associated stored access policy. Azure will always
118            convert values to UTC. If a date is passed in without timezone info, it
119            is assumed to be UTC.
120        :type expiry: datetime or str
121        :param start:
122            The time at which the shared access signature becomes valid. If
123            omitted, start time for this call is assumed to be the time when the
124            storage service receives the request. Azure will always convert values
125            to UTC. If a date is passed in without timezone info, it is assumed to
126            be UTC.
127        :type start: datetime or str
128        :param str ip:
129            Specifies an IP address or a range of IP addresses from which to accept requests.
130            If the IP address from which the request originates does not match the IP address
131            or address range specified on the SAS token, the request is not authenticated.
132            For example, specifying sip=168.1.5.65 or sip=168.1.5.60-168.1.5.70 on the SAS
133            restricts the request to those IP addresses.
134        :param str protocol:
135            Specifies the protocol permitted for a request made. The default value
136            is https,http. See :class:`~azure.storage.common.models.Protocol` for possible values.
137        '''
138        sas = _SharedAccessHelper()
139        sas.add_base(permission, expiry, start, ip, protocol, self.x_ms_version)
140        sas.add_account(services, resource_types)
141        sas.add_account_signature(self.account_name, self.account_key)
142
143        return sas.get_token()
144
145
146class _SharedAccessHelper(object):
147    def __init__(self):
148        self.query_dict = {}
149
150    def _add_query(self, name, val):
151        if val:
152            self.query_dict[name] = _str(val) if val is not None else None
153
154    def add_base(self, permission, expiry, start, ip, protocol, x_ms_version):
155        if isinstance(start, date):
156            start = _to_utc_datetime(start)
157
158        if isinstance(expiry, date):
159            expiry = _to_utc_datetime(expiry)
160
161        self._add_query(QueryStringConstants.SIGNED_START, start)
162        self._add_query(QueryStringConstants.SIGNED_EXPIRY, expiry)
163        self._add_query(QueryStringConstants.SIGNED_PERMISSION, permission)
164        self._add_query(QueryStringConstants.SIGNED_IP, ip)
165        self._add_query(QueryStringConstants.SIGNED_PROTOCOL, protocol)
166        self._add_query(QueryStringConstants.SIGNED_VERSION, x_ms_version)
167
168    def add_resource(self, resource):
169        self._add_query(QueryStringConstants.SIGNED_RESOURCE, resource)
170
171    def add_id(self, policy_id):
172        self._add_query(QueryStringConstants.SIGNED_IDENTIFIER, policy_id)
173
174    def add_account(self, services, resource_types):
175        self._add_query(QueryStringConstants.SIGNED_SERVICES, services)
176        self._add_query(QueryStringConstants.SIGNED_RESOURCE_TYPES, resource_types)
177
178    def add_override_response_headers(self, cache_control,
179                                      content_disposition,
180                                      content_encoding,
181                                      content_language,
182                                      content_type):
183        self._add_query(QueryStringConstants.SIGNED_CACHE_CONTROL, cache_control)
184        self._add_query(QueryStringConstants.SIGNED_CONTENT_DISPOSITION, content_disposition)
185        self._add_query(QueryStringConstants.SIGNED_CONTENT_ENCODING, content_encoding)
186        self._add_query(QueryStringConstants.SIGNED_CONTENT_LANGUAGE, content_language)
187        self._add_query(QueryStringConstants.SIGNED_CONTENT_TYPE, content_type)
188
189    def add_account_signature(self, account_name, account_key):
190        def get_value_to_append(query):
191            return_value = self.query_dict.get(query) or ''
192            return return_value + '\n'
193
194        string_to_sign = \
195            (account_name + '\n' +
196             get_value_to_append(QueryStringConstants.SIGNED_PERMISSION) +
197             get_value_to_append(QueryStringConstants.SIGNED_SERVICES) +
198             get_value_to_append(QueryStringConstants.SIGNED_RESOURCE_TYPES) +
199             get_value_to_append(QueryStringConstants.SIGNED_START) +
200             get_value_to_append(QueryStringConstants.SIGNED_EXPIRY) +
201             get_value_to_append(QueryStringConstants.SIGNED_IP) +
202             get_value_to_append(QueryStringConstants.SIGNED_PROTOCOL) +
203             get_value_to_append(QueryStringConstants.SIGNED_VERSION))
204
205        self._add_query(QueryStringConstants.SIGNED_SIGNATURE,
206                        sign_string(account_key, string_to_sign))
207
208    def get_token(self):
209        return '&'.join(['{0}={1}'.format(n, url_quote(v)) for n, v in self.query_dict.items() if v is not None])
210