Back to home page

EIC code displayed by LXR

 
 

    


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

0001 """User-authority REST surface — the interface swf-remote's sign-in sweep writes.
0002 
0003 swf-remote resolves `eic` GitHub organisation membership at sign-in and posts
0004 it here; swf-monitor stores it and, from step 3 of the rollout, enforces on
0005 it. JSON in, JSON out, never a redirect, so the call survives the swf-remote
0006 hop (swf-monitor docs/EXTERNAL_ACCESS.md).
0007 
0008 The endpoint writes the observed field only. ``rights`` is granted by a person
0009 in the User admin page and is refused here, so a sign-in cannot alter an
0010 administrative decision and the field's provenance holds by construction
0011 rather than by convention.
0012 
0013 Writing authority is not a user capability. The caller must be the swf-remote
0014 authority writer arriving over the localhost tunnel — a service identity,
0015 never a person — or an authenticated superuser on this side. Anything else is
0016 refused, including a signed-in user acting for themselves, so the endpoint
0017 stays closed even if it is ever reachable through the proxy.
0018 
0019 Model and rollout: docs/AUTHORITY.md.
0020 """
0021 
0022 from django.http import JsonResponse
0023 from rest_framework.authentication import (SessionAuthentication,
0024                                            TokenAuthentication)
0025 from rest_framework.decorators import (api_view, authentication_classes,
0026                                        permission_classes)
0027 from rest_framework.permissions import AllowAny
0028 
0029 from monitor_app.authority import (AUTHORITY_WRITER, AuthorityError,
0030                                    set_check_failed, set_eic)
0031 from monitor_app.middleware import TunnelAuthentication, is_tunnel_request
0032 
0033 _AUTH = [TunnelAuthentication, SessionAuthentication, TokenAuthentication]
0034 
0035 
0036 def _is_authority_writer(request):
0037     """True for the swf-remote authority writer, or a superuser on this side."""
0038     user = getattr(request, 'user', None)
0039     if not user or not user.is_authenticated:
0040         return False
0041     if is_tunnel_request(request) and user.username == AUTHORITY_WRITER:
0042         return True
0043     return bool(getattr(user, 'is_superuser', False))
0044 
0045 
0046 @api_view(['POST'])
0047 @authentication_classes(_AUTH)
0048 @permission_classes([AllowAny])
0049 def user_authority(request):
0050     """POST /api/user-authority/
0051 
0052     ``{"username": "...", "authority": {"eic": true, "github": "..."}}``
0053     ``{"username": "...", "authority": {"check_failed": "...", "github": "..."}}``
0054 
0055     The second form records a sign-in whose membership check reached no
0056     answer; it leaves ``eic`` alone and is cleared by the next observation.
0057 
0058     Records observed organisation membership for the account, and the GitHub
0059     login it was observed for — several accounts have a username unlike their
0060     GitHub login, and an administrator deciding a grant needs to see the
0061     identity that was checked. ``eic: null`` clears the field to unset.
0062 
0063     Returns ``{"username": ..., "authority": {"eic": …, "rights": …,
0064     "github": …}}``, the full record including the rights this endpoint does
0065     not write.
0066     """
0067     if not _is_authority_writer(request):
0068         return JsonResponse(
0069             {'error': 'not authorized to write user authority'}, status=403)
0070 
0071     body = request.data if isinstance(request.data, dict) else {}
0072     username = (body.get('username') or '').strip()
0073     if not username:
0074         return JsonResponse({'error': 'username is required'}, status=400)
0075 
0076     values = body.get('authority')
0077     if not isinstance(values, dict):
0078         return JsonResponse(
0079             {'error': 'authority must be an object'}, status=400)
0080     if 'rights' in values or 'rights' in body:
0081         return JsonResponse(
0082             {'error': 'rights is granted by a person in the User admin page '
0083                       'and cannot be written here'}, status=400)
0084     unknown = sorted(set(values) - {'eic', 'github', 'check_failed'})
0085     if unknown:
0086         return JsonResponse(
0087             {'error': f"unknown authority field(s): {', '.join(unknown)}; "
0088                       'this endpoint writes eic, github and check_failed'},
0089             status=400)
0090     if ('eic' in values) == ('check_failed' in values):
0091         return JsonResponse(
0092             {'error': 'exactly one of eic (an observation) or check_failed '
0093                       '(a check that reached no answer) is required'},
0094             status=400)
0095 
0096     github = values.get('github', body.get('github'))
0097     try:
0098         if 'check_failed' in values:
0099             record = set_check_failed(username, values.get('check_failed'),
0100                                       github=github)
0101         else:
0102             record = set_eic(username, values.get('eic'), github=github)
0103     except AuthorityError as e:
0104         return JsonResponse({'error': str(e)}, status=400)
0105     return JsonResponse({'username': username, 'authority': record})