Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New APIs Part 1 #2

Merged
merged 8 commits into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 2 additions & 22 deletions mxlive/lims/stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from mxlive.lims.models import Data, Sample, Session, Project, AnalysisReport, Container, Shipment, ProjectType, SupportArea, UserFeedback, UserAreaFeedback, SupportRecord, FeedbackScale, DataType
from mxlive.utils.functions import ShiftEnd, ShiftStart, ShiftIndex
from mxlive.utils.misc import humanize_duration, natural_duration
from mxlive.utils.stats import make_table

HOUR_SECONDS = 3600
SHIFT = getattr(settings, "HOURS_PER_SHIFT", 8)
Expand All @@ -41,27 +42,6 @@ def get_data_periods(period='year'):
return sorted(Data.objects.values_list(field, flat=True).order_by(field).distinct())


def make_table(data, columns, rows, total_col=True, total_row=True):
''' Converts a list of dictionaries into a list of lists ready for displaying as a table
data: list of dictionaries (one dictionary per column header)
columns: list of column headers to display in table, ordered the same as data
rows: list of row headers to display in table
'''
header_row = [''] + columns
if total_col: header_row += ['All']
table_data = [[str(r)] + [0] * (len(header_row) - 1) for r in rows]
for row in table_data:
for i, val in enumerate(data):
row[i+1] = val.get(row[0], 0)
if total_col:
row[-1] = sum(row[1:-1])
if total_row:
footer_row = ['Total'] + [0] * (len(header_row) - 1)
for i in range(len(footer_row)-1):
footer_row[i+1] = sum([d[i+1] for d in table_data])
return [header_row] + table_data + [footer_row]


def get_time_scale(filters):
period = filters.get('time_scale', 'year')
if period == 'month':
Expand Down Expand Up @@ -599,7 +579,7 @@ def parameter_summary(**filters):
'kind': 'histogram',
'data': {
'data': [
{"x": row[0], "y": row[1]} for row in param_histograms[param]
{"x": float(row[0]), "y": float(row[1])} for row in param_histograms[param]
],
},
'style': 'col-12 col-md-6'
Expand Down
59 changes: 56 additions & 3 deletions mxlive/remote/middleware.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from cryptography.exceptions import InvalidSignature
from django.contrib.auth import get_user_model
from django.http import HttpResponseRedirect
from django.conf import settings
from django.http import Http404
Expand All @@ -6,21 +8,26 @@
from ipaddress import ip_address
from ipaddress import ip_network

from django.urls import resolve
from django.utils.deprecation import MiddlewareMixin
from rest_framework_simplejwt.authentication import JWTAuthentication, AuthenticationFailed
from mxlive.utils.signing import Signer

TRUSTED_URLS = getattr(settings, 'TRUSTED_URLS', [])
TRUSTED_IPS = getattr(settings, 'TRUSTED_IPS', ['127.0.0.1/32'])


class IPAddressList(list):
def __init__(self, *ips):
super().__init__()
self.extend([ip_network(ip) for ip in ips])
self.extend([ip_network(ip, False) for ip in ips])

def __contains__(self, address):
ip = ip_address(address)
return any(ip in net for net in self)


class PermissionsMiddleware(object):
class PermissionsMiddleware(MiddlewareMixin):

def process_request(self, request):
if request.user.is_superuser:
Expand All @@ -46,7 +53,7 @@ def get_client_address(request):
return address and address.exploded or address


class TrustedAccessMiddleware(object):
class TrustedAccessMiddleware(MiddlewareMixin):
"""
Middleware to prevent access to the admin if the user IP
isn't in the TRUSTED_IPS setting.
Expand All @@ -66,3 +73,49 @@ def process_template_response(self, request, response):
if response.context_data:
response.context_data['internal_request'] = (client_address in trusted_addresses)
return response


def get_v2_user(request):
url_data = resolve(request.path)
kwargs = url_data.kwargs
if 'username' in kwargs and 'signature' in kwargs:
user_model = get_user_model()
try:
user = user_model.objects.get(username=kwargs.get('username'))
except user_model.DoesNotExist:
return None
else:
if not user.key:
return None

try:
signer = Signer(public=user.key)
value = signer.unsign(kwargs.get('signature'))
except InvalidSignature:
return None
else:
if value != kwargs.get('username'):
return None
return user


def get_v3_user(request):
authenticator = JWTAuthentication()
try:
user_data = authenticator.authenticate(request)
except AuthenticationFailed:
user_data = None
if user_data:
return user_data[0]


class APIAuthenticationMiddleware(MiddlewareMixin):
def process_request(self, request):
user = None
if request.path.startswith('/api/v2'):
user = get_v2_user(request)
elif request.path.startswith('/api/v3'):
user = get_v3_user(request)

if user:
request.user = user
30 changes: 16 additions & 14 deletions mxlive/remote/urls.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
from django.urls import re_path, path
from django.urls import path
from . import views


def keyed_url(regex, view, kwargs=None, name=None):
regex = r'(?P<signature>(?P<username>[\w_-]+):.+)/' + regex[1:]
return re_path(regex, view, kwargs, name)

from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
TokenVerifyView
)

urlpatterns = [
re_path(r'^accesslist/$', views.AccessList.as_view()),
path('keys/<slug:username>', views.SSHKeys.as_view(), name='project-sshkeys'),
path('auth/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('auth/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
path('auth/verify/', TokenVerifyView.as_view(), name='token_verify'),

keyed_url(r'^data/(?P<beamline>[\w_-]+)/$', views.AddData.as_view()),
keyed_url(r'^report/(?P<beamline>[\w_-]+)/$', views.AddReport.as_view()),
path('accesslist/', views.AccessList.as_view()),
path('keys/<slug:username>/', views.SSHKeys.as_view(), name='project-sshkeys'),

keyed_url(r'^project/$', views.UpdateUserKey.as_view(), name='project-update'),
keyed_url(r'^samples/(?P<beamline>[\w_-]+)/$', views.ProjectSamples.as_view(), name='project-samples'),
keyed_url(r'^launch/(?P<beamline>[\w_-]+)/(?P<session>[\w_-]+)/$', views.LaunchSession.as_view(), name='session-launch'),
keyed_url(r'^close/(?P<beamline>[\w_-]+)/(?P<session>[\w_-]+)/$', views.CloseSession.as_view(), name='session-close'),
path('data/<slug:beamline>/', views.AddData.as_view()),
path('report/<slug:beamline>/', views.AddReport.as_view()),
path('samples/<slug:beamline>/', views.ProjectSamples.as_view(), name='project-samples'),
path('session/<slug:beamline>/<slug:session>/start/', views.LaunchSession.as_view(), name='session-launch'),
path('session/<slug:beamline>/<slug:session>/close/', views.CloseSession.as_view(), name='session-close'),
]
21 changes: 21 additions & 0 deletions mxlive/remote/urls_v2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from django.urls import re_path, path
from . import views


def keyed_url(regex, view, kwargs=None, name=None):
regex = r'(?P<signature>(?P<username>[\w_-]+):.+)/' + regex[1:]
return re_path(regex, view, kwargs, name)


urlpatterns = [
re_path(r'^accesslist/$', views.AccessList.as_view()),
path('keys/<slug:username>', views.SSHKeys.as_view(), name='project-sshkeys'),

keyed_url(r'^data/(?P<beamline>[\w_-]+)/$', views.AddData.as_view()),
keyed_url(r'^report/(?P<beamline>[\w_-]+)/$', views.AddReport.as_view()),

keyed_url(r'^project/$', views.UpdateUserKey.as_view(), name='project-update'),
keyed_url(r'^samples/(?P<beamline>[\w_-]+)/$', views.ProjectSamples.as_view(), name='project-samples'),
keyed_url(r'^launch/(?P<beamline>[\w_-]+)/(?P<session>[\w_-]+)/$', views.LaunchSession.as_view(), name='session-launch'),
keyed_url(r'^close/(?P<beamline>[\w_-]+)/(?P<session>[\w_-]+)/$', views.CloseSession.as_view(), name='session-close'),
]
Loading