Skip to content

Commit 160e08c

Browse files
authored
Add basic JWT setup (#297)
Add functionality to generate and use tokens for basic authorisation. Intend to extend
1 parent 4e3171b commit 160e08c

File tree

19 files changed

+320
-36
lines changed

19 files changed

+320
-36
lines changed

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,10 @@ server = [
6060
"jinja2",
6161
"numpy",
6262
"packaging",
63+
"passlib",
6364
"pillow",
6465
"prometheus_client",
66+
"python-jose[cryptography]",
6567
"readlif", # Specific to cryo-CLEM workflow
6668
"sqlmodel",
6769
"stomp-py<=8.1.0", # 8.1.1 (released 2024-04-06) doesn't work with our project
@@ -75,6 +77,7 @@ Documentation = "https://github.com/DiamondLightSource/python-murfey"
7577
GitHub = "https://github.com/DiamondLightSource/python-murfey"
7678
[project.scripts]
7779
murfey = "murfey.client:run"
80+
"murfey.add_user" = "murfey.cli.add_user:run"
7881
"murfey.create_db" = "murfey.cli.create_db:run"
7982
"murfey.db_sql" = "murfey.cli.murfey_db_sql:run"
8083
"murfey.decrypt_password" = "murfey.cli.decrypt_db_password:run"

src/murfey/cli/add_user.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import argparse
2+
import os
3+
4+
from sqlmodel import Session, create_engine
5+
6+
from murfey.server.auth import hash_password
7+
from murfey.server.config import get_machine_config
8+
from murfey.server.murfey_db import url
9+
from murfey.util.db import MurfeyUser as User
10+
11+
12+
def run():
13+
parser = argparse.ArgumentParser(
14+
description="Generate the necessary tables for the Murfey database"
15+
)
16+
17+
parser.add_argument("-u", "--username", type=str, help="User name for new user")
18+
parser.add_argument("-p", "--password", type=str, help="Password for new user")
19+
parser.add_argument(
20+
"-m",
21+
"--microscope",
22+
dest="microscope",
23+
type=str,
24+
default="",
25+
help="Microscope as specified in the Murfey machine configuration",
26+
)
27+
28+
args = parser.parse_args()
29+
if args.microscope:
30+
os.environ["BEAMLINE"] = args.microscope
31+
32+
new_user = User(
33+
username=args.username, hashed_password=hash_password(args.password)
34+
)
35+
_url = url(get_machine_config())
36+
engine = create_engine(_url)
37+
with Session(engine) as murfey_db:
38+
murfey_db.add(new_user)
39+
murfey_db.commit()
40+
murfey_db.close()

src/murfey/client/__init__.py

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import time
1313
import webbrowser
1414
from datetime import datetime
15+
from functools import partial
1516
from pathlib import Path
1617
from queue import Queue
1718
from typing import List, Literal
@@ -41,6 +42,29 @@
4142
log = logging.getLogger("murfey.client")
4243

4344

45+
def read_config() -> configparser.ConfigParser:
46+
config = configparser.ConfigParser()
47+
try:
48+
mcch = os.environ.get("MURFEY_CLIENT_CONFIG_HOME")
49+
murfey_client_config_home = Path(mcch) if mcch else Path.home()
50+
with open(murfey_client_config_home / ".murfey") as configfile:
51+
config.read_file(configfile)
52+
except FileNotFoundError:
53+
log.warning(
54+
f"Murfey client configuration file {murfey_client_config_home / '.murfey'} not found"
55+
)
56+
if "Murfey" not in config:
57+
config["Murfey"] = {}
58+
return config
59+
60+
61+
token = read_config()["Murfey"].get("token", "")
62+
63+
requests.get = partial(requests.get, headers={"Authorization": f"Bearer {token}"})
64+
requests.post = partial(requests.post, headers={"Authorization": f"Bearer {token}"})
65+
requests.delete = partial(requests.delete, headers={"Authorization": f"Bearer {token}"})
66+
67+
4468
def _enable_webbrowser_in_cygwin():
4569
"""Helper function to make webbrowser.open() work in CygWin"""
4670
if "cygwin" in platform.system().lower() and shutil.which("cygstart"):
@@ -317,20 +341,6 @@ def main_loop(
317341
time.sleep(15)
318342

319343

320-
def read_config() -> configparser.ConfigParser:
321-
config = configparser.ConfigParser()
322-
try:
323-
mcch = os.environ.get("MURFEY_CLIENT_CONFIG_HOME")
324-
murfey_client_config_home = Path(mcch) if mcch else Path.home()
325-
with open(murfey_client_config_home / ".murfey") as configfile:
326-
config.read_file(configfile)
327-
except FileNotFoundError:
328-
pass
329-
if "Murfey" not in config:
330-
config["Murfey"] = {}
331-
return config
332-
333-
334344
def write_config(config: configparser.ConfigParser):
335345
mcch = os.environ.get("MURFEY_CLIENT_CONFIG_HOME")
336346
murfey_client_config_home = Path(mcch) if mcch else Path.home()

src/murfey/client/contexts/fib.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@
1010

1111
from murfey.client.context import Context
1212
from murfey.client.instance_environment import MurfeyInstanceEnvironment
13+
from murfey.util import authorised_requests
1314

1415
logger = logging.getLogger("murfey.client.contexts.fib")
1516

17+
requests.get, requests.post, requests.put, requests.delete = authorised_requests()
18+
1619

1720
class Lamella(NamedTuple):
1821
name: str

src/murfey/client/contexts/spa.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,17 @@
1515
MurfeyID,
1616
MurfeyInstanceEnvironment,
1717
)
18-
from murfey.util import capture_get, capture_post, get_machine_config
18+
from murfey.util import (
19+
authorised_requests,
20+
capture_get,
21+
capture_post,
22+
get_machine_config,
23+
)
1924

2025
logger = logging.getLogger("murfey.client.contexts.spa")
2126

27+
requests.get, requests.post, requests.put, requests.delete = authorised_requests()
28+
2229

2330
class FoilHole(NamedTuple):
2431
session_id: int

src/murfey/client/contexts/spa_metadata.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@
88
from murfey.client.context import Context
99
from murfey.client.contexts.spa import _get_grid_square_atlas_positions, _get_source
1010
from murfey.client.instance_environment import MurfeyInstanceEnvironment, SampleInfo
11-
from murfey.util import capture_post, get_machine_config
11+
from murfey.util import authorised_requests, capture_post, get_machine_config
1212

1313
logger = logging.getLogger("murfey.client.contexts.spa_metadata")
1414

15+
requests.get, requests.post, requests.put, requests.delete = authorised_requests()
16+
1517

1618
def _atlas_destination(
1719
environment: MurfeyInstanceEnvironment, source: Path, file_path: Path

src/murfey/client/contexts/tomo.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@
1919
MurfeyInstanceEnvironment,
2020
global_env_lock,
2121
)
22-
from murfey.util import capture_post, get_machine_config
22+
from murfey.util import authorised_requests, capture_post, get_machine_config
2323
from murfey.util.mdoc import get_block, get_global_data, get_num_blocks
2424

2525
logger = logging.getLogger("murfey.client.contexts.tomo")
2626

27+
requests.get, requests.post, requests.put, requests.delete = authorised_requests()
28+
2729

2830
class TiltInfoExtraction(NamedTuple):
2931
series: Callable[[Path], str]

src/murfey/client/tui/app.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,23 @@
3232
from murfey.client.tui.status_bar import StatusBar
3333
from murfey.client.watchdir import DirWatcher
3434
from murfey.client.watchdir_multigrid import MultigridDirWatcher
35-
from murfey.util import capture_post, get_machine_config, set_default_acquisition_output
35+
from murfey.util import (
36+
capture_post,
37+
get_machine_config,
38+
read_config,
39+
set_default_acquisition_output,
40+
)
3641

3742
log = logging.getLogger("murfey.tui.app")
3843

3944
ReactiveType = TypeVar("ReactiveType")
4045

46+
token = read_config()["Murfey"].get("token", "")
47+
48+
requests.get = partial(requests.get, headers={"Authorization": f"Bearer {token}"})
49+
requests.post = partial(requests.post, headers={"Authorization": f"Bearer {token}"})
50+
requests.delete = partial(requests.delete, headers={"Authorization": f"Bearer {token}"})
51+
4152

4253
class MurfeyTUI(App):
4354
CSS_PATH = "controller.css"

src/murfey/client/tui/screens.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,19 @@
5656
)
5757
from murfey.client.rsync import RSyncer
5858
from murfey.client.tui.forms import FormDependency
59-
from murfey.util import capture_post, get_machine_config
59+
from murfey.util import capture_post, get_machine_config, read_config
6060
from murfey.util.models import PreprocessingParametersTomo, ProcessingParametersSPA
6161

6262
log = logging.getLogger("murfey.tui.screens")
6363

6464
ReactiveType = TypeVar("ReactiveType")
6565

66+
token = read_config()["Murfey"].get("token", "")
67+
68+
requests.get = partial(requests.get, headers={"Authorization": f"Bearer {token}"})
69+
requests.post = partial(requests.post, headers={"Authorization": f"Bearer {token}"})
70+
requests.delete = partial(requests.delete, headers={"Authorization": f"Bearer {token}"})
71+
6672

6773
def determine_default_destination(
6874
visit: str,

src/murfey/server/api.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
import packaging.version
1111
import sqlalchemy
12-
from fastapi import APIRouter, Request
12+
from fastapi import APIRouter, Depends, Request
1313
from fastapi.responses import FileResponse, HTMLResponse
1414
from ispyb.sqlalchemy import AutoProcProgram as ISPyBAutoProcProgram
1515
from ispyb.sqlalchemy import (
@@ -46,6 +46,7 @@
4646
sanitise,
4747
templates,
4848
)
49+
from murfey.server.auth import validate_token
4950
from murfey.server.config import from_file, settings
5051
from murfey.server.gain import Camera, prepare_eer_gain, prepare_gain
5152
from murfey.server.murfey_db import murfey_db
@@ -108,7 +109,7 @@
108109

109110
machine_config = get_machine_config()
110111

111-
router = APIRouter()
112+
router = APIRouter(dependencies=[Depends(validate_token)])
112113

113114

114115
# This will be the homepage for a given microscope.

0 commit comments

Comments
 (0)