-
Notifications
You must be signed in to change notification settings - Fork 10
/
providers.py
153 lines (125 loc) · 4.69 KB
/
providers.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
from abc import ABC, abstractmethod
from lxml import etree
from requests.exceptions import RequestException
from urllib.parse import urlencode
import logging
import requests
logger = logging.getLogger('captcha')
class VerificationError(Exception):
'''Raised when verification could not be completed, such as when the provider returns an error.'''
class CaptchaProvider(ABC):
@property
@abstractmethod
def verification_url(self):
pass
@property
@abstractmethod
def response_form_key(self):
pass
@classmethod
def parse(cls, string):
if string is None:
raise ValueError('Captcha provider unspecified. Options include "Google" and "hCpatcha"')
if string.lower() in ['google', 'recaptcha', 're']:
return ReCaptchaProvider
if string.lower() in ['intuition machines', 'hcaptcha', 'h']:
return HCaptchaProvider
raise ValueError('{!r} could not be parsed as a captcha provider')
@abstractmethod
def script_tag(self):
pass
@abstractmethod
def challenge_tag(self):
pass
def post_data(self, response, remote_ip=None):
data = {
'secret': self.secret,
'response': response,
}
if self.verify_remote_ip:
if remote_ip is None:
logger.error("Configured to verify remote IP, but remote IP was not provided.")
raise VerificationError()
data['remoteip'] = remote_ip
return data
def verify(self, form, remote_ip=None):
# If the form did not include a captcha response, fail immediately.
if not form.get(self.response_form_key, None):
return False
# Make the request to the captcha provider.
post_data = self.post_data(form[self.response_form_key], remote_ip)
logger.debug("Sending captcha verification request {} to {}".format(post_data, self.verification_url))
verify_reponse = requests.post(self.verification_url, data=post_data)
# If the HTTP request failed, bail.
try:
verify_reponse.raise_for_status()
except RequestException as e:
logger.error("Captcha request failed with code {}".format(verify_response.status_code))
raise VerificationError() from e
# Parse the response and check for error codes.
verify = verify_reponse.json()
logger.debug("Got captcha verification response: {}".format(verify))
if verify.get('error-codes', None):
logger.error("Captcha service returned error codes {}".format(verify['error-codes']))
raise VerificationError()
return verify['success']
class ReCaptchaProvider(CaptchaProvider):
verification_url = 'https://www.google.com/recaptcha/api/siteverify'
response_form_key = 'g-recaptcha-response'
def __init__(self, site_key, secret, verify_remote_ip=False):
self.site_key = site_key
self.secret = secret
self.verify_remote_ip = verify_remote_ip
def script_tag(self):
return etree.Element(
'script',
attrib = {
'src': 'https://www.google.com/recaptcha/api.js',
'async': 'true',
'defer': 'true'
}
)
def challenge_tag(self):
return etree.Element(
'div',
attrib = {
'class': 'g-recaptcha float-left',
'data-sitekey': self.site_key,
}
)
class HCaptchaProvider(CaptchaProvider):
verification_url = 'https://hcaptcha.com/siteverify'
response_form_key = 'h-captcha-response'
def __init__(self, site_key, secret, verify_remote_ip=False):
self.site_key = site_key
self.secret = secret
self.verify_remote_ip = verify_remote_ip
def script_tag(self):
return etree.Element(
'script',
attrib = {
'src': 'https://hcaptcha.com/1/api.js',
'async': 'true',
'defer': 'true'
}
)
def challenge_tag(self):
return etree.Element(
'div',
attrib = {
'class': 'h-captcha float-left',
'data-sitekey': self.site_key,
}
)
def post_data(self, response, remote_ip=None):
data = {
'secret': self.secret,
'sitekey': self.site_key,
'response': response,
}
if self.verify_remote_ip:
if remote_ip is None:
logger.error("Configured to verify remote IP, but remote IP was not provided.")
raise VerificationError()
data['remoteip'] = remote_ip
return data