1
|
# mandayejs - saml reverse proxy
|
2
|
# Copyright (C) 2015 Entr'ouvert
|
3
|
#
|
4
|
# This program is free software: you can redistribute it and/or modify it
|
5
|
# under the terms of the GNU Affero General Public License as published
|
6
|
# by the Free Software Foundation, either version 3 of the License, or
|
7
|
# (at your option) any later version.
|
8
|
#
|
9
|
# This program is distributed in the hope that it will be useful,
|
10
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
# GNU Affero General Public License for more details.
|
13
|
#
|
14
|
# You should have received a copy of the GNU Affero General Public License
|
15
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
16
|
import os
|
17
|
import re
|
18
|
import json
|
19
|
import subprocess
|
20
|
import logging
|
21
|
import multiprocessing
|
22
|
import urlparse
|
23
|
|
24
|
from django.conf import settings
|
25
|
from django.shortcuts import resolve_url
|
26
|
|
27
|
from Cookie import SimpleCookie
|
28
|
|
29
|
|
30
|
logger = logging.getLogger(__name__)
|
31
|
|
32
|
|
33
|
def run(send_end, data, script):
|
34
|
phantom = subprocess.Popen([
|
35
|
settings.PHANTOM_JS_BINARY,
|
36
|
'--ignore-ssl-errors=yes', '--ssl-protocol=any',
|
37
|
os.path.join(settings.BASE_DIR, 'mandayejs', script)],
|
38
|
close_fds=True,
|
39
|
stdin=subprocess.PIPE,
|
40
|
stdout=subprocess.PIPE
|
41
|
)
|
42
|
stdout, stderr = phantom.communicate(json.dumps(data))
|
43
|
|
44
|
try:
|
45
|
output = re.search('<mandayejs>(.*?)</mandayejs>', stdout, re.DOTALL)
|
46
|
if not output:
|
47
|
raise ValueError
|
48
|
stdout = output.group(1)
|
49
|
result = json.loads(stdout)
|
50
|
except (ValueError,):
|
51
|
result = {"result": "json_error"}
|
52
|
logger.error("invalid json: %s" % stdout)
|
53
|
|
54
|
if result.get('stderr'):
|
55
|
logger.warning(result['stderr'])
|
56
|
if result.get('error'):
|
57
|
logger.error('Error occured: %s' % result.get('reason'))
|
58
|
|
59
|
send_end.send(result)
|
60
|
send_end.close()
|
61
|
|
62
|
|
63
|
def exec_phantom(data, script='do_login.js'):
|
64
|
recv_end, send_end = multiprocessing.Pipe(False)
|
65
|
process = multiprocessing.Process(target=run, args=(send_end, data, script))
|
66
|
process.start()
|
67
|
|
68
|
if recv_end.poll(settings.PHANTOM_JS_TIMEOUT):
|
69
|
result = recv_end.recv()
|
70
|
recv_end.close()
|
71
|
else:
|
72
|
process.terminate()
|
73
|
send_end.close()
|
74
|
# Don't log locators, they may contain credentials (passwords)
|
75
|
context = {k: v for k, v in data.items() if k != 'locators'}
|
76
|
logger.error("PhantomJS process timeout, context: %s" % context)
|
77
|
result = {'result': 'timeout'}
|
78
|
|
79
|
return result
|
80
|
|
81
|
|
82
|
def cookie_builder(headers):
|
83
|
"""Build Cookies from list of headers
|
84
|
"""
|
85
|
cookie = SimpleCookie()
|
86
|
for header in headers:
|
87
|
cookie.load('; '.join(header.values()).encode('ascii'))
|
88
|
|
89
|
return cookie
|
90
|
|
91
|
|
92
|
def get_logout_info(request):
|
93
|
"""Returns phantomjs logout prerequis
|
94
|
"""
|
95
|
from mandayejs.applications import get_app_settings
|
96
|
app_settings = get_app_settings()
|
97
|
|
98
|
data = {}
|
99
|
data['logout_locator'] = getattr(app_settings, 'SITE_LOGOUT_LOCATOR')
|
100
|
data['address'] = request.build_absolute_uri(resolve_url('home'))
|
101
|
forced_logout_scheme = getattr(settings, 'PHANTOM_JS_LOGOUT_SCHEME')
|
102
|
if forced_logout_scheme:
|
103
|
url = urlparse.urlparse(data['address'])
|
104
|
url = url._replace(scheme=forced_logout_scheme)
|
105
|
data['address'] = url.geturl()
|
106
|
cookies = SimpleCookie(request.META.get('HTTP_COOKIE'))
|
107
|
domain = request.META.get('SERVER_NAME')
|
108
|
|
109
|
# Phantomjs Cookies Format
|
110
|
data['cookies'] = [{
|
111
|
'name': key,
|
112
|
'value': value.value,
|
113
|
'domain': domain,
|
114
|
'path': '/'
|
115
|
} for key, value in cookies.items()]
|
116
|
|
117
|
return data
|
118
|
|
119
|
|
120
|
def get_password_field():
|
121
|
"""Return name of the password field
|
122
|
"""
|
123
|
from mandayejs.applications import get_app_settings
|
124
|
app_settings = get_app_settings()
|
125
|
try:
|
126
|
field_name = [field.get('name') for field in app_settings.SITE_LOCATORS
|
127
|
if field.get('kind') == 'password']
|
128
|
return field_name[0]
|
129
|
except (IndexError,):
|
130
|
return None
|
131
|
|
132
|
|
133
|
def get_login_info(request, credentials):
|
134
|
"""Returns phantomjs login prerequis
|
135
|
"""
|
136
|
from mandayejs.applications import get_app_settings
|
137
|
app_settings = get_app_settings()
|
138
|
auth_checker = os.path.join(
|
139
|
settings.STATIC_ROOT, app_settings.SITE_AUTH_CHECKER)
|
140
|
return {
|
141
|
'address': request.build_absolute_uri(app_settings.SITE_LOGIN_PATH),
|
142
|
'cookies': [],
|
143
|
'locators': [credentials.to_login_info()],
|
144
|
'auth_checker': auth_checker,
|
145
|
'form_submit_element': app_settings.SITE_FORM_SUBMIT_ELEMENT
|
146
|
}
|
147
|
|
148
|
|
149
|
def get_idp():
|
150
|
"""Return idp
|
151
|
"""
|
152
|
return settings.MELLON_IDENTITY_PROVIDERS[0]['METADATA_URL']
|