1#!/usr/bin/env python3
2
3"""Configure Django user settings."""
4
5import json
6
7from django.urls import reverse
8from django.http import HttpResponse
9from django.shortcuts import render
10from django.shortcuts import redirect
11from django.views.decorators.cache import never_cache
12from django.contrib import auth
13from django.contrib.auth.models import User as DjangoUser
14
15from chart.db.connection import db_connect
16from chart.project import settings
17from chart.events.eventclass import EventClass
18from chart.web.user import User
19from chart.project import SID
20
21db_conn = db_connect() # TBD: used for event_subscriptions, auth_user, users
22
23# user's email address must end with one of these otherwise it's rejected
24VALID_EMAIL_SUFFIXES = ['@eumetsat.int', '@external.eumetsat.int']
25
26
27def validate_email_address(address):
28 """Check `address` is a valid email address for a configured user.
29 We cannot send emails outside the eumetsat.int domain.
30 Return `True` if everything looks ok, otherwise an error message as a string.
31 """
32
33 if any(address.endswith(s) for s in VALID_EMAIL_SUFFIXES):
34 return True
35
36 else:
37 return 'Non-EUMETSAT addresses are not allowed'
38
39
40def index(request, user_id=None): # (unused parameter) pylint:disable=W0613
41 """Return top level index page."""
42
43 if not request.user.is_authenticated or request.user.id is None:
44 return redirect(reverse('homepage:index'))
45 # return HttpResponse('<html><body><p>Not logged in</p></body></html>')
46
47 event_classes = [{'name': e.name,
48 'properties': list(e.instance_properties.keys())} for e in EventClass.all()]
49
50 row = db_conn.query('SELECT daily_digest FROM users WHERE django_user_id=:id',
51 id=request.user.id).fetchone()
52 if row is None:
53 # this user has not accessed the CHART user info page before
54 init_user(request.user.id)
55 row = db_conn.query('SELECT daily_digest '
56 'FROM users '
57 'WHERE django_user_id=:id',
58 id=request.user.id).fetchone()
59
60 (daily_digest,) = row
61
62 user_subs = [{'classname': row[0],
63 # this isn't really scid it should be sid but the old name is used all over
64 # the js code
65 'scid': SID.from_sys_select('USER_SUBSCRIPTIONS', row[4:]),
66 'property': row[1] if row[1] is not None else '',
67 'op': row[2] if row[2] is not None else '',
68 'value': row[3] if row[3] is not None else ''} for row in db_conn.query(
69 'SELECT event_classname, property_name, '\
70 'property_test, property_value, {sid} FROM event_subscriptions '\
71 'WHERE django_user_id=:id'.format(sid=','.join(SID.sql_sys_select('EVENT_SUBSCRIPTIONS'))),
72 id=request.user.id)]
73
74 if request.user.is_superuser:
75 users = [row[0] for row in db_conn.query('SELECT username FROM auth_user')]
76 users.sort()
77
78 else:
79 users = None
80
81 #get current user to later check whether he or she can publish
82 if request.user.id is not None:
83 my_user = User(user_id=request.user.id)
84 can_publish = my_user.can_publish_reports
85 data_access = my_user.define_data_access
86
87 else:
88 can_publish = False
89 data_access = False
90
91 return render(request,
92 'web/userinfo.html',
93 dict(next=request.GET.get('next', reverse('homepage:index')),
94 event_classes=json.dumps(event_classes),
95 daily_digest=daily_digest,
96 user_subs=user_subs,
97 users=users,
98 can_publish=can_publish,
99 data_access=data_access,
100 sids=SID.all()))
101
102
103def init_user(user_id):
104 """Create a new, default entry in the USERS table."""
105
106 db_conn.query('INSERT INTO USERS (django_user_id, daily_digest, notifications) '
107 'VALUES (:user_id, :daily_digest, :notifications)',
108 user_id=user_id,
109 daily_digest=0, # Oracle has no boolean type so we use int 0/1
110 notifications=0) # notifications no longer used but DB schema requires it
111 db_conn.commit()
112
113
114@never_cache
115def save(request):
116 """Store any changes to user configuration in AUTH_USERS, USERS and EVENT_SUBSCRIPTIONS."""
117
118 # print 'request.GET ', request.GET
119 first_name = request.GET['first_name']
120 last_name = request.GET['last_name']
121 email = request.GET['email']
122 daily_digest = 1 if request.GET['daily_digest'] == 'true' else 0
123 events = json.loads(request.GET['events'])
124
125 # check email address is ok
126 valid = validate_email_address(email)
127 if valid is not True:
128 return HttpResponse(json.dumps({'colour': '#ff0000',
129 'message': valid}),
130 content_type='text/json')
131
132 for e in events:
133 for p in ('classname', 'scid', 'property', 'op', 'value'):
134 if e[p] == '':
135 e[p] = None
136
137 changes = []
138
139 if db_conn.query('SELECT count(*) FROM users WHERE django_user_id=:id',
140 id=request.user.id).fetchone()[0] == 0:
141 init_user(request.user.id)
142 changes.append('user initialised')
143
144 row = db_conn.query('SELECT first_name, last_name, email FROM auth_user WHERE id=:id',
145 id=request.user.id).fetchone()
146 if row[0] != first_name or row[1] != last_name:
147 db_conn.query('UPDATE auth_user SET first_name=:first_name, last_name=:last_name '
148 'WHERE id=:user_id',
149 user_id=request.user.id,
150 first_name=first_name,
151 last_name=last_name)
152 # This looks like it's not needed. However we've sen some cases of a connection
153 # left hanging which might be caused by an exception thrown later on, leaving the
154 # connection with uncomitted changes
155 db_conn.commit()
156 changes.append('name changed')
157
158 if row[2] != email:
159 db_conn.query('UPDATE auth_user SET email=:email '
160 'WHERE id=:user_id',
161 user_id=request.user.id,
162 email=email)
163 db_conn.commit()
164 changes.append('email changed')
165
166 # update daily digest subscription
167 row = db_conn.query('SELECT daily_digest FROM users WHERE django_user_id=:id',
168 id=request.user.id).fetchone()
169 if row[0] != daily_digest:
170 db_conn.query('UPDATE users SET daily_digest=:daily_digest WHERE django_user_id=:id',
171 daily_digest=daily_digest,
172 id=request.user.id)
173 db_conn.commit()
174 changes.append('daily digest subscription updated')
175
176 # test if event subscriptions have changed
177 # existing_subs = set(((i for i in db_conn.query(
178 # 'SELECT event_classname, scid, property_name, property_test, property_value '
179 # 'FROM event_subscriptions WHERE django_user_id=:id',
180 # id=request.user.id))))
181 existing_subs = set()
182 for row in db_conn.query(
183 'SELECT event_classname, property_name, property_test, property_value, {sid} '\
184 'FROM event_subscriptions WHERE django_user_id=:id'.format(
185 sid=','.join(SID.sql_sys_select('EVENT_SUBSCRIPTIONS'))),
186 id=request.user.id):
187 existing_subs.add((row[0], row[1], row[2], row[3], SID.from_sys_select('EVENT_SUBSCRIPTIONS', row[4:])))
188
189 new_subs = set((((e['classname'],
190 e['property'],
191 e['op'],
192 e['value'],
193 SID(e['scid'])) for e in events)))
194
195 sid_fields, sid_binds = SID.sql_sys_insert('EVENT_SUBSCRIPTIONS')
196 ins_cur = db_conn.prepared_cursor(
197 'INSERT INTO event_subscriptions (DJANGO_USER_ID, EVENT_CLASSNAME, PROPERTY_NAME, '
198 'PROPERTY_TEST, PROPERTY_VALUE{sidfield}) VALUES (:id, :classname, :property, '
199 ':test, :value{sidbind})'.format(sidfield=sid_fields, sidbind=sid_binds))
200
201 # We squash the lists of existing and required subscriptions together and rebuild
202 # all subscriptions if there are any changes
203 if (len(existing_subs) != len(new_subs) or
204 len(existing_subs.intersection(new_subs)) != len(new_subs)):
205
206 # rebuild the entries in the EVENT_SUBSCRIPTIONS table
207 db_conn.query('DELETE FROM event_subscriptions WHERE django_user_id=:id',
208 id=request.user.id)
209
210 for e in events:
211 ins_cur.execute(None,
212 id=request.user.id,
213 classname=e['classname'],
214 property=e['property'],
215 test=e['op'],
216 value=e['value'],
217 **SID.bind_sys_insert('JOBS', SID(e['scid'])))
218
219 db_conn.commit()
220 changes.append('event subscriptions updated')
221
222 # Send a status message back to the user
223 if len(changes) > 0:
224 db_conn.commit()
225 colour = '#00b000' # pale green
226 message = (', '.join(changes)).capitalize()
227
228 else:
229 colour = '#0000b0' # blue
230 message = 'No changes to save'
231
232 return HttpResponse(json.dumps({'colour': colour,
233 'message': message}),
234 content_type='text/json')
235
236
237def test_email(request):
238 """Send a test email to the address entered by the user in the userinfo form."""
239 email = request.GET['email']
240 first_name = request.GET['first_name']
241 last_name = request.GET['last_name']
242 valid = validate_email_address(email)
243 if valid is not True:
244 return HttpResponse(json.dumps({'colour': '#ff0000',
245 'message': valid}),
246 content_type='text/json')
247
248 from chart.common.sendmail import sendmail
249 sendmail(from_address=(settings.EMAIL_NAME.format('test'),
250 settings.EMAIL_ADDRESS.format('test')),
251 to_addresses=((first_name + ' ' + last_name,
252 email),),
253 subject=settings.EMAIL_SUBJECT_PREFIX + 'Test email',
254 message='This is a test message.\n' + settings.EMAIL_MESSAGE_SUFFIX)
255 return HttpResponse(json.dumps({'colour': '#00b000',
256 'message': 'Message sent ok'}),
257 content_type='text/json')
258
259
260def switch_user(request):
261 """Tell Django to change the currently logged in user."""
262
263 if request.user.is_superuser:
264 new_username = request.GET['username']
265 user = DjangoUser.objects.get(username__exact=new_username)
266 # trick Django into thinking this user logged in properly
267 auth.login(request, user, 'django.contrib.auth.backends.ModelBackend')
268
269 return HttpResponse('')