]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/dashboard/awsauth.py
import 15.2.0 Octopus source
[ceph.git] / ceph / src / pybind / mgr / dashboard / awsauth.py
CommitLineData
11fdf7f2
TL
1# -*- coding: utf-8 -*-
2# pylint: disable-all
3#
4# Copyright (c) 2012-2013 Paul Tax <paultax@gmail.com> All rights reserved.
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions are
8# met:
9#
10# 1. Redistributions of source code must retain the above copyright
11# notice, this list of conditions and the following disclaimer.
12#
13# 2. Redistributions in binary form must reproduce the above copyright
14# notice, this list of conditions and the following disclaimer in
15# the documentation and/or other materials provided with the
16# distribution.
17#
18# 3. Neither the name of Infrae nor the names of its contributors may
19# be used to endorse or promote products derived from this software
20# without specific prior written permission.
21#
22# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
25# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INFRAE OR
26# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
27# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
28# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
29# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
30# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
31# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
32# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33
34import hmac
35
36from hashlib import sha1 as sha
37
38py3k = False
39try:
40 from urlparse import urlparse, unquote
41 from base64 import encodestring
42except ImportError:
43 py3k = True
44 from urllib.parse import urlparse, unquote
45 from base64 import encodebytes as encodestring
46
47from email.utils import formatdate
48
49from requests.auth import AuthBase
50
51
52class S3Auth(AuthBase):
53
54 """Attaches AWS Authentication to the given Request object."""
55
56 service_base_url = 's3.amazonaws.com'
57 # List of Query String Arguments of Interest
58 special_params = [
59 'acl', 'location', 'logging', 'partNumber', 'policy', 'requestPayment',
60 'torrent', 'versioning', 'versionId', 'versions', 'website', 'uploads',
61 'uploadId', 'response-content-type', 'response-content-language',
62 'response-expires', 'response-cache-control', 'delete', 'lifecycle',
63 'response-content-disposition', 'response-content-encoding', 'tagging',
64 'notification', 'cors'
65 ]
66
67 def __init__(self, access_key, secret_key, service_url=None):
68 if service_url:
69 self.service_base_url = service_url
70 self.access_key = str(access_key)
71 self.secret_key = str(secret_key)
72
73 def __call__(self, r):
74 # Create date header if it is not created yet.
75 if 'date' not in r.headers and 'x-amz-date' not in r.headers:
76 r.headers['date'] = formatdate(
77 timeval=None,
78 localtime=False,
79 usegmt=True)
80 signature = self.get_signature(r)
81 if py3k:
82 signature = signature.decode('utf-8')
83 r.headers['Authorization'] = 'AWS %s:%s' % (self.access_key, signature)
84 return r
85
86 def get_signature(self, r):
87 canonical_string = self.get_canonical_string(
88 r.url, r.headers, r.method)
89 if py3k:
90 key = self.secret_key.encode('utf-8')
91 msg = canonical_string.encode('utf-8')
92 else:
9f95a23c 93 key = self.secret_key # type: ignore
11fdf7f2
TL
94 msg = canonical_string
95 h = hmac.new(key, msg, digestmod=sha)
96 return encodestring(h.digest()).strip()
97
98 def get_canonical_string(self, url, headers, method):
99 parsedurl = urlparse(url)
100 objectkey = parsedurl.path[1:]
101 query_args = sorted(parsedurl.query.split('&'))
102
103 bucket = parsedurl.netloc[:-len(self.service_base_url)]
104 if len(bucket) > 1:
105 # remove last dot
106 bucket = bucket[:-1]
107
108 interesting_headers = {
109 'content-md5': '',
110 'content-type': '',
111 'date': ''}
112 for key in headers:
113 lk = key.lower()
114 try:
115 if isinstance(lk, bytes):
116 lk = lk.decode('utf-8')
117 except UnicodeDecodeError:
118 pass
119 if headers[key] and (lk in interesting_headers.keys()
120 or lk.startswith('x-amz-')):
121 interesting_headers[lk] = headers[key].strip()
122
123 # If x-amz-date is used it supersedes the date header.
124 if not py3k:
125 if 'x-amz-date' in interesting_headers:
126 interesting_headers['date'] = ''
127 else:
128 if 'x-amz-date' in interesting_headers:
129 interesting_headers['date'] = ''
130
131 buf = '%s\n' % method
132 for key in sorted(interesting_headers.keys()):
133 val = interesting_headers[key]
134 if key.startswith('x-amz-'):
135 buf += '%s:%s\n' % (key, val)
136 else:
137 buf += '%s\n' % val
138
139 # append the bucket if it exists
140 if bucket != '':
141 buf += '/%s' % bucket
142
143 # add the objectkey. even if it doesn't exist, add the slash
144 buf += '/%s' % objectkey
145
146 params_found = False
147
148 # handle special query string arguments
149 for q in query_args:
150 k = q.split('=')[0]
151 if k in self.special_params:
152 buf += '&' if params_found else '?'
153 params_found = True
154
155 try:
156 k, v = q.split('=', 1)
157
158 except ValueError:
159 buf += q
160
161 else:
162 # Riak CS multipart upload ids look like this, `TFDSheOgTxC2Tsh1qVK73A==`,
163 # is should be escaped to be included as part of a query string.
164 #
165 # A requests mp upload part request may look like
166 # resp = requests.put(
167 # 'https://url_here',
168 # params={
169 # 'partNumber': 1,
170 # 'uploadId': 'TFDSheOgTxC2Tsh1qVK73A=='
171 # },
172 # data='some data',
173 # auth=S3Auth('access_key', 'secret_key')
174 # )
175 #
176 # Requests automatically escapes the values in the `params` dict, so now
177 # our uploadId is `TFDSheOgTxC2Tsh1qVK73A%3D%3D`,
178 # if we sign the request with the encoded value the signature will
179 # not be valid, we'll get 403 Access Denied.
180 # So we unquote, this is no-op if the value isn't encoded.
181 buf += '{key}={value}'.format(key=k, value=unquote(v))
182
183 return buf