14 |
14 |
# You should have received a copy of the GNU Affero General Public License
|
15 |
15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
16 |
16 |
|
|
17 |
|
|
18 |
import base64
|
|
19 |
import datetime
|
17 |
20 |
import os
|
18 |
21 |
import logging
|
19 |
|
import requests
|
20 |
22 |
import urlparse
|
21 |
23 |
import hashlib
|
|
24 |
import hmac
|
|
25 |
import random
|
22 |
26 |
from html2text import HTML2Text
|
23 |
27 |
from emails.django import Message
|
24 |
28 |
from lxml import etree
|
25 |
29 |
|
|
30 |
from requests import Response, Session as RequestsSession, RequestException
|
|
31 |
|
26 |
32 |
from django.conf import settings
|
27 |
33 |
from django.template import loader, Context
|
28 |
34 |
from django.utils.translation import activate
|
|
35 |
from django.utils.http import urlencode, quote
|
29 |
36 |
from django.core.files.storage import DefaultStorage
|
30 |
37 |
from django.core.urlresolvers import reverse
|
31 |
38 |
from django.core import signing
|
32 |
39 |
|
33 |
40 |
|
|
41 |
# Simple signature scheme for query strings
|
|
42 |
|
|
43 |
def sign_url(url, key, algo='sha256', timestamp=None, nonce=None):
|
|
44 |
parsed = urlparse.urlparse(url)
|
|
45 |
new_query = sign_query(parsed.query, key, algo, timestamp, nonce)
|
|
46 |
return urlparse.urlunparse(parsed[:4] + (new_query,) + parsed[5:])
|
|
47 |
|
|
48 |
def sign_query(query, key, algo='sha256', timestamp=None, nonce=None):
|
|
49 |
if timestamp is None:
|
|
50 |
timestamp = datetime.datetime.utcnow()
|
|
51 |
timestamp = timestamp.strftime('%Y-%m-%dT%H:%M:%SZ')
|
|
52 |
if nonce is None:
|
|
53 |
nonce = hex(random.getrandbits(128))[2:]
|
|
54 |
new_query = query
|
|
55 |
if new_query:
|
|
56 |
new_query += '&'
|
|
57 |
new_query += urlencode((
|
|
58 |
('algo', algo),
|
|
59 |
('timestamp', timestamp),
|
|
60 |
('nonce', nonce)))
|
|
61 |
signature = base64.b64encode(sign_string(new_query, key, algo=algo))
|
|
62 |
new_query += '&signature=' + quote(signature)
|
|
63 |
return new_query
|
|
64 |
|
|
65 |
def sign_string(s, key, algo='sha256', timedelta=30):
|
|
66 |
digestmod = getattr(hashlib, algo)
|
|
67 |
hash = hmac.HMAC(str(key), digestmod=digestmod, msg=s)
|
|
68 |
return hash.digest()
|
|
69 |
|
|
70 |
|
|
71 |
class Requests(RequestsSession):
|
|
72 |
def request(self, method, url, **kwargs):
|
|
73 |
logger = logging.getLogger(__name__)
|
|
74 |
log_errors = kwargs.pop('log_errors', True)
|
|
75 |
|
|
76 |
remote_service = None
|
|
77 |
scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
|
|
78 |
for services in getattr(settings, 'KNOWN_SERVICES', {}).values():
|
|
79 |
for service in services.values():
|
|
80 |
remote_url = service.get('url')
|
|
81 |
remote_scheme, remote_netloc, r_path, r_params, r_query, r_fragment = \
|
|
82 |
urlparse.urlparse(remote_url)
|
|
83 |
if remote_scheme == scheme and remote_netloc == netloc:
|
|
84 |
remote_service = service
|
|
85 |
break
|
|
86 |
else:
|
|
87 |
continue
|
|
88 |
break
|
|
89 |
if remote_service:
|
|
90 |
# only keeps the path (URI) in url parameter, scheme and netloc are
|
|
91 |
# in remote_service
|
|
92 |
url = urlparse.urlunparse(('', '', path, params, query, fragment))
|
|
93 |
else:
|
|
94 |
logger.warning('service not found in settings.KNOWN_SERVICES for %s', url)
|
|
95 |
|
|
96 |
if remote_service:
|
|
97 |
query_params = {'orig': remote_service.get('orig')}
|
|
98 |
|
|
99 |
remote_service_base_url = remote_service.get('url')
|
|
100 |
scheme, netloc, old_path, params, old_query, fragment = urlparse.urlparse(
|
|
101 |
remote_service_base_url)
|
|
102 |
|
|
103 |
query = urlencode(query_params)
|
|
104 |
if '?' in url:
|
|
105 |
path, old_query = url.split('?')
|
|
106 |
query += '&' + old_query
|
|
107 |
else:
|
|
108 |
path = url
|
|
109 |
|
|
110 |
url = urlparse.urlunparse((scheme, netloc, path, params, query, fragment))
|
|
111 |
|
|
112 |
if remote_service: # sign
|
|
113 |
url = sign_url(url, remote_service.get('secret'))
|
|
114 |
|
|
115 |
response = super(Requests, self).request(method, url, **kwargs)
|
|
116 |
if log_errors and (response.status_code // 100 != 2):
|
|
117 |
logger.error('failed to %s %s (%s)', method, url, response.status_code)
|
|
118 |
return response
|
|
119 |
|
|
120 |
requests = Requests()
|
|
121 |
|
|
122 |
|
34 |
123 |
UNSUBSCRIBE_LINK_PLACEHOLDER = '%%UNSUBSCRIBE_LINK_PLACEHOLDER%%'
|
35 |
124 |
|
36 |
125 |
|
... | ... | |
90 |
179 |
'from': settings.SMS_EXPEDITOR}
|
91 |
180 |
try:
|
92 |
181 |
response = requests.post(settings.SMS_GATEWAY_URL, json=data, proxies=settings.REQUESTS_PROXIES)
|
93 |
|
response.raise_for_status()
|
94 |
182 |
if not response.json()['err']:
|
95 |
183 |
# if no error returned by SMS gateway presume the that content
|
96 |
184 |
# was delivered to all destinations
|
97 |
185 |
sent = len(destinations)
|
98 |
186 |
else:
|
99 |
187 |
logger.warning('Error occured while sending sms: %s', response.json()['err_desc'])
|
100 |
|
except requests.RequestException as e:
|
|
188 |
except RequestException as e:
|
101 |
189 |
logger.warning('Failed to reach SMS gateway: %s', e)
|
102 |
190 |
return sent
|
103 |
191 |
else:
|