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('')