Back to home page

EIC code displayed by LXR

 
 

    


Warning, file /swf-monitor/src/monitor_app/viewdir/user_admin.py was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 """User admin — the page where rights are granted, and the account's own view.
0002 
0003 Two surfaces over `monitor_app.authority`:
0004 
0005 - ``user_admin_page``, under the System menu, open to staff and to accounts
0006   holding ``rights == 'ops'``. It lists accounts with the GitHub login their
0007   membership was observed for, that membership, their rights, and whether
0008   they may act. ``eic`` is display-only: it is observed from GitHub and a
0009   hand edit would be overwritten at the next sign-in.
0010 - ``user_rights_set``, the JSON endpoint the page writes through, and the
0011   only path by which ``rights`` is written. Keeping it apart from
0012   ``/api/user-authority/`` is what makes the split structural: the endpoint
0013   every sign-in goes through cannot touch rights at all.
0014 
0015 The write is a JSON POST rather than a page-view POST because the page is
0016 served to external users through the swf-remote proxy, which carries no
0017 session or CSRF and cannot relay a redirect (docs/EXTERNAL_ACCESS.md).
0018 
0019 Model: docs/AUTHORITY.md.
0020 """
0021 
0022 import json
0023 
0024 from django.contrib.auth import get_user_model
0025 from django.http import JsonResponse
0026 from django.shortcuts import render
0027 from django.views.decorators.http import require_http_methods
0028 
0029 from monitor_app.authority import (OPS_MEANING, PAC_MEANING, RIGHTS_BASIC,
0030                                    RIGHTS_MEANING, RIGHTS_READ,
0031                                    AuthorityError, all_authority,
0032                                    get_authority, is_ops, may_act,
0033                                    may_set_priority, set_ops, set_pac,
0034                                    set_rights)
0035 
0036 #: The access levels the page offers; operations is a role beside them.
0037 ACCESS_VALUES = (RIGHTS_READ, RIGHTS_BASIC)
0038 
0039 
0040 def may_administer(user):
0041     """Staff on this side, or an account holding operations rights."""
0042     if not user or not user.is_authenticated:
0043         return False
0044     return bool(user.is_staff) or is_ops(user.username)
0045 
0046 
0047 def _origin(record):
0048     """Where the account's identity comes from, as far as the record says.
0049 
0050     A GitHub identity is one the sweep has observed membership for. Rights
0051     without one is an account established inside the BNL perimeter through
0052     the account sync. Neither is an account nothing has been written for
0053     yet, and the column says nothing rather than guessing.
0054     """
0055     if record['github'] or record['eic'] is not None:
0056         return 'GitHub'
0057     if record['rights']:
0058         return 'account sync'
0059     return ''
0060 
0061 
0062 def _rows():
0063     """Every account known on this side, with its authority."""
0064     User = get_user_model()
0065     records = all_authority()
0066     logins = dict(User.objects.values_list('username', 'last_login'))
0067     staff = set(User.objects.filter(is_staff=True)
0068                 .values_list('username', flat=True))
0069     rows = []
0070     for username in sorted(set(records) | set(logins), key=str.lower):
0071         record = records.get(username) or get_authority(username)
0072         rows.append({
0073             'username': username,
0074             'github': record['github'],
0075             'origin': _origin(record),
0076             'eic': record['eic'],
0077             'rights': (RIGHTS_BASIC if record['rights'] == 'ops'
0078                        else record['rights']),
0079             'pac': record['pac'],
0080             'ops': record['ops'],
0081             'may_act': may_act(record),
0082             'may_set_priority': may_set_priority(record),
0083             'last_seen': record['eic_at'] or '',
0084             'check_failed': record['check_failed'],
0085             'check_failed_at': record['check_failed_at'],
0086             'last_login': logins.get(username),
0087             'is_staff': username in staff,
0088         })
0089     return rows
0090 
0091 
0092 @require_http_methods(['GET'])
0093 def user_admin_page(request):
0094     """GET users/admin/ — the account list, and where rights are set."""
0095     if not may_administer(request.user):
0096         return render(request, 'monitor_app/user_admin.html',
0097                       {'forbidden': True}, status=403)
0098     rows = _rows()
0099     return render(request, 'monitor_app/user_admin.html', {
0100         'rows': rows,
0101         'rights_values': ACCESS_VALUES,
0102         'rights_levels': [{'value': v, 'meaning': RIGHTS_MEANING[v]}
0103                           for v in ACCESS_VALUES],
0104         'pac_meaning': PAC_MEANING,
0105         'ops_meaning': OPS_MEANING,
0106         'acting_count': sum(1 for r in rows if r['may_act']),
0107         'pac_count': sum(1 for r in rows if r['pac']),
0108         'ops_count': sum(1 for r in rows if r['ops']),
0109     })
0110 
0111 
0112 @require_http_methods(['POST'])
0113 def user_rights_set(request):
0114     """POST /api/user-rights/ ``{"username": "...", "rights": "basic"}``,
0115     ``{"username": "...", "pac": true}``, ``{"username": "...", "ops": true}``,
0116     or any of them together.
0117 
0118     ``rights: null`` clears the grant, returning the account to drawing
0119     authority from ``eic`` alone; ``pac: false`` and ``ops: false`` clear
0120     the roles. All are a person's fields, written through this one
0121     endpoint and never by the sign-in sweep. Returns the account's full
0122     record.
0123     """
0124     from monitor_app.authority import AUTHORITY_WRITER
0125     from monitor_app.middleware import is_tunnel_request
0126 
0127     user = request.user
0128     writer = (user.is_authenticated and is_tunnel_request(request)
0129               and user.username == AUTHORITY_WRITER)
0130     if not (may_administer(user) or writer):
0131         return JsonResponse(
0132             {'error': 'not authorized to set rights'}, status=403)
0133 
0134     try:
0135         body = json.loads(request.body or b'{}')
0136     except ValueError as e:
0137         return JsonResponse({'error': f'malformed JSON: {e}'}, status=400)
0138     if not isinstance(body, dict):
0139         return JsonResponse({'error': 'body must be an object'}, status=400)
0140 
0141     username = (body.get('username') or '').strip()
0142     if not username:
0143         return JsonResponse({'error': 'username is required'}, status=400)
0144     if 'eic' in body:
0145         return JsonResponse(
0146             {'error': 'eic is observed at sign-in and cannot be set here'},
0147             status=400)
0148 
0149     if not any(k in body for k in ('rights', 'pac', 'ops')):
0150         return JsonResponse({'error': 'rights, pac or ops is required'},
0151                             status=400)
0152     try:
0153         record = None
0154         if 'rights' in body:
0155             rights = body.get('rights')
0156             if rights == '':
0157                 rights = None
0158             record = set_rights(username, rights)
0159         if 'pac' in body:
0160             record = set_pac(username, bool(body.get('pac')))
0161         if 'ops' in body:
0162             record = set_ops(username, bool(body.get('ops')))
0163     except AuthorityError as e:
0164         return JsonResponse({'error': str(e)}, status=400)
0165     return JsonResponse({'username': username, 'authority': record,
0166                          'may_act': may_act(record),
0167                          'may_set_priority': may_set_priority(record)})