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})