Skip to content

Commit

Permalink
added oauth
Browse files Browse the repository at this point in the history
  • Loading branch information
ZohebShaikh authored and DiamondJoseph committed Nov 5, 2024
1 parent 5abe34a commit 97e5275
Show file tree
Hide file tree
Showing 17 changed files with 1,062 additions and 84 deletions.
75 changes: 36 additions & 39 deletions dev-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
accessible-pygments==0.0.5
aioca==1.8
aioca==1.8.1
aiofiles==24.1.0
aiohappyeyeballs==2.4.3
aiohttp==3.10.10
Expand All @@ -11,9 +11,8 @@ appdirs==1.4.4
asciitree==0.3.3
asgiref==3.8.1
asttokens==2.4.1
async-timeout==4.0.3
asyncapi-schema-pydantic==1.0.1
attrs==24.2.0
autodoc_pydantic==2.2.0
babel==2.16.0
beautifulsoup4==4.12.3
bidict==0.23.1
Expand All @@ -22,24 +21,25 @@ bluesky==1.13.0a3
bluesky-kafka==0.10.0
bluesky-live==0.0.8
bluesky-stomp==0.1.2
boltons==24.0.0
bump-pydantic==0.8.0
boltons==24.1.0
cachetools==5.5.0
caproto==1.1.1
certifi==2024.8.30
cffi==1.17.1
cfgv==3.4.0
chardet==5.2.0
charset-normalizer==3.4.0
click==8.1.7
cloudpickle==3.1.0
colorama==0.4.6
colorlog==6.8.2
colorlog==6.9.0
comm==0.2.2
compress-pickle==2.1.0
confluent-kafka==2.6.0
contourpy==1.3.0
copier==9.4.1
coverage==7.6.4
cryptography==43.0.3
cycler==0.12.1
dask==2024.10.0
databroker==1.2.5
Expand All @@ -51,7 +51,7 @@ Deprecated==1.2.14
diff_cover==9.2.0
distlib==0.3.9
dls-bluesky-core==0.0.4
dls-dodal==1.33.0
dls-dodal==1.34.1
dnspython==2.7.0
docopt==0.6.2
doct==1.1.0
Expand All @@ -61,9 +61,8 @@ email_validator==2.2.0
entrypoints==0.4
epicscorelibs==7.0.7.99.1.1
event-model==1.21.0
exceptiongroup==1.2.2
executing==2.1.0
fastapi==0.115.3
fastapi==0.115.4
fastapi-cli==0.0.5
fasteners==0.19
filelock==3.16.1
Expand All @@ -77,13 +76,13 @@ gitdb==4.0.11
GitPython==3.1.43
googleapis-common-protos==1.65.0
graypy==2.1.0
grpcio==1.66.2
grpcio==1.67.1
h11==0.14.0
h5py==3.12.1
HeapDict==1.0.1
historydict==1.2.6
httpcore==1.0.6
httptools==0.6.1
httptools==0.6.4
httpx==0.27.2
humanize==4.11.0
identify==2.6.1
Expand All @@ -100,19 +99,16 @@ itsdangerous==2.2.0
jedi==0.19.1
Jinja2==3.1.4
jinja2-ansible-filters==1.3.2
jsonpointer==3.0.0
jsonschema==4.23.0
jsonschema-specifications==2024.10.1
jupyterlab_widgets==3.0.13
kiwisolver==1.4.7
ldap3==2.9.1
libcst==1.4.0
livereload==2.7.0
locket==1.0.0
lz4==4.3.3
markdown-it-py==3.0.0
MarkupSafe==3.0.2
marshmallow==3.23.0
marshmallow==3.23.1
matplotlib==3.9.2
matplotlib-inline==0.1.7
mdit-py-plugins==0.4.2
Expand All @@ -129,10 +125,11 @@ myst-parser==4.0.0
networkx==3.4.2
nodeenv==1.9.1
nose2==0.15.1
nslsii==0.10.5
nslsii==0.10.7
numcodecs==0.13.1
numpy==1.26.4
observability-utils==0.1.2
opencv-python==4.10.0.84
opencv-python-headless==4.10.0.84
opentelemetry-api==1.27.0
opentelemetry-distro==0.48b0
Expand All @@ -148,9 +145,9 @@ opentelemetry-sdk==1.27.0
opentelemetry-semantic-conventions==0.48b0
opentelemetry-util-http==0.48b0
ophyd==1.9.0
ophyd-async==0.6.0
ophyd-async==0.7.0
orderly-set==5.2.2
orjson==3.10.10
orjson==3.10.11
p4p==4.2.0
packaging==24.1
pandas==2.2.3
Expand All @@ -170,7 +167,7 @@ pluggy==1.5.0
plumbum==1.9.0
ply==3.11
pre_commit==4.0.1
prettytable==3.11.0
prettytable==3.12.0
prompt-toolkit==3.0.36
propcache==0.2.0
protobuf==4.25.5
Expand All @@ -180,44 +177,48 @@ pure_eval==0.2.3
pvxslibs==1.3.2
py==1.11.0
pyasn1==0.6.1
pycparser==2.22
pycryptodome==3.21.0
pydantic==2.9.2
pydantic-extra-types==2.9.0
pydantic-settings==2.6.0
pydantic-asyncapi==0.2.1
pydantic-extra-types==2.10.0
pydantic-settings==2.6.1
pydantic_core==2.23.4
pydantic_numpy==5.0.2
pydata-sphinx-theme==0.16.0
pyepics==3.5.7
Pygments==2.18.0
PyJWT==2.9.0
pymongo==4.10.1
pyOlog==4.5.0
pyparsing==3.2.0
pyright==1.1.386
pytest==8.3.3
pytest-asyncio==0.24.0
pytest-cov==5.0.0
pytest-cov==6.0.0
pytest-random-order==1.1.1
python-dateutil==2.9.0.post0
python-dotenv==1.0.1
python-multipart==0.0.9
python-multipart==0.0.17
pytz==2024.2
PyYAML==6.0.2
pyyaml-include==2.1
questionary==2.0.1
redis==5.2.0
redis-json-dict==0.2.1
referencing==0.35.1
requests==2.32.3
responses==0.25.3
rich==13.7.1
rpds-py==0.20.0
rich==13.9.4
rpds-py==0.20.1
ruamel.yaml==0.18.6
ruamel.yaml.clib==0.2.12
ruff==0.7.1
ruff==0.7.2
scanspec==0.7.6
semver==3.0.2
setuptools==75.3.0
setuptools-dso==2.11
shellingham==1.5.4
shortuuid==1.0.13
six==1.16.0
slicerator==1.1.0
smmap==5.0.1
Expand All @@ -229,8 +230,6 @@ sphinx-autobuild==2024.10.3
sphinx-autodoc-typehints==2.3.0
sphinx-click==6.0.0
sphinx-copybutton==0.5.2
sphinx-jsonschema==1.19.1
sphinx-pydantic==0.1.1
sphinx_design==0.6.1
sphinx_mdinclude==0.6.2
sphinxcontrib-applehelp==2.0.0
Expand All @@ -243,21 +242,19 @@ sphinxcontrib-openapi==0.8.4
sphinxcontrib-qthelp==2.0.0
sphinxcontrib-serializinghtml==2.0.0
stack-data==0.6.3
starlette==0.41.0
stomp-py==8.1.2
starlette==0.41.2
stomp.py==8.2.0
suitcase-mongo==0.6.0
suitcase-msgpack==0.3.0
suitcase-utils==0.5.4
super-state-machine==2.0.2
tifffile==2024.9.20
tomli==2.0.1
toolz==1.0.0
tornado==6.4.1
tox==3.28.0
tox-direct==0.4
tqdm==4.66.5
tqdm==4.66.6
traitlets==5.14.3
typer==0.12.4
typer==0.12.5
types-aiofiles==24.1.0.20240626
types-mock==5.1.0.20240425
types-PyYAML==6.0.12.20240917
Expand All @@ -270,17 +267,17 @@ tzlocal==5.2
ujson==5.10.0
urllib3==2.2.3
uvicorn==0.32.0
uvloop==0.19.0
virtualenv==20.27.0
uvloop==0.21.0
virtualenv==20.27.1
watchfiles==0.24.0
wcwidth==0.2.13
websocket-client==1.8.0
websockets==13.1
widgetsnbextension==4.0.13
workflows==2.27
workflows==2.28
wrapt==1.16.0
xarray==2024.10.0
yarl==1.16.0
yarl==1.17.1
zarr==2.18.3
zict==2.2.0
zipp==3.20.2
Expand Down
7 changes: 5 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,16 @@ dependencies = [
"requests",
"dls-bluesky-core", #requires ophyd-async
"dls-dodal>=1.31.0",
"super-state-machine", # https://github.com/DiamondLightSource/blueapi/issues/553
"super-state-machine", # https://github.com/DiamondLightSource/blueapi/issues/553
"GitPython",
"bluesky-stomp>=0.1.2",
"event-model==1.21", # https://github.com/DiamondLightSource/blueapi/issues/684
"event-model==1.21", # https://github.com/DiamondLightSource/blueapi/issues/684
"opentelemetry-distro>=0.48b0",
"opentelemetry-instrumentation-fastapi>=0.48b0",
"observability-utils>=0.1.2",
"pyjwt",
"python-multipart",
"cryptography",
]
dynamic = ["version"]
license.file = "LICENSE"
Expand Down
39 changes: 38 additions & 1 deletion src/blueapi/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,13 @@
from blueapi.client.client import BlueapiClient
from blueapi.client.event_bus import AnyEvent, BlueskyStreamingError, EventBusClient
from blueapi.client.rest import BlueskyRemoteControlError
from blueapi.config import ApplicationConfig, ConfigLoader
from blueapi.config import (
ApplicationConfig,
CLIClientConfig,
ConfigLoader,
)
from blueapi.core import OTLP_EXPORT_ENABLED, DataEvent
from blueapi.service.authentication import CliTokenManager, SessionManager
from blueapi.worker import ProgressEvent, Task, WorkerEvent

from .scratch import setup_scratch
Expand Down Expand Up @@ -346,3 +351,35 @@ def scratch(obj: dict) -> None:
setup_scratch(config.scratch)
else:
raise KeyError("No scratch config supplied")


@main.command(name="login")
@click.pass_obj
def login(obj: dict) -> None:
config: ApplicationConfig = obj["config"]
if isinstance(config.oauth_client, CLIClientConfig) and config.oauth_server:
print("Logging in")
auth: SessionManager = SessionManager(
server_config=config.oauth_server,
client_config=config.oauth_client,
token_manager=CliTokenManager(Path(config.oauth_client.token_file_path)),
)
auth.start_device_flow()
else:
print("Please provide configuration to login!")


@main.command(name="logout")
@click.pass_obj
def logout(obj: dict) -> None:
config: ApplicationConfig = obj["config"]
if isinstance(config.oauth_client, CLIClientConfig) and config.oauth_server:
auth: SessionManager = SessionManager(
server_config=config.oauth_server,
client_config=config.oauth_client,
token_manager=CliTokenManager(Path(config.oauth_client.token_file_path)),
)
auth.logout()
print("Logged out")
else:
print("Please provide configuration to logout!")
6 changes: 5 additions & 1 deletion src/blueapi/client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from blueapi.config import ApplicationConfig
from blueapi.core.bluesky_types import DataEvent
from blueapi.service.authentication import SessionManager
from blueapi.service.model import (
DeviceModel,
DeviceResponse,
Expand Down Expand Up @@ -45,7 +46,10 @@ def __init__(

@classmethod
def from_config(cls, config: ApplicationConfig) -> "BlueapiClient":
rest = BlueapiRestClient(config.api)
rest: BlueapiRestClient = BlueapiRestClient(
config.api,
SessionManager.from_config(config.oauth_server, config.oauth_client),
)
if config.stomp is not None:
template = StompClient.for_broker(
broker=Broker(
Expand Down
20 changes: 19 additions & 1 deletion src/blueapi/client/rest.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from collections.abc import Callable, Mapping
from typing import Any, Literal, TypeVar

import jwt
import requests
from observability_utils.tracing import (
get_context_propagator,
Expand All @@ -10,6 +11,7 @@
from pydantic import TypeAdapter

from blueapi.config import RestConfig
from blueapi.service.authentication import SessionManager
from blueapi.service.model import (
DeviceModel,
DeviceResponse,
Expand Down Expand Up @@ -45,8 +47,13 @@ def _exception(response: requests.Response) -> Exception | None:
class BlueapiRestClient:
_config: RestConfig

def __init__(self, config: RestConfig | None = None) -> None:
def __init__(
self,
config: RestConfig | None = None,
session_manager: SessionManager | None = None,
) -> None:
self._config = config or RestConfig()
self._session_manager: SessionManager | None = session_manager

def get_plans(self) -> PlanResponse:
return self._request_and_deserialize("/plans", PlanResponse)
Expand Down Expand Up @@ -137,6 +144,17 @@ def _request_and_deserialize(
url = self._url(suffix)
# Get the trace context to propagate to the REST API
carr = get_context_propagator()
# Attach authentication information if present
carr["content-type"] = "application/json; charset=UTF-8"
if self._session_manager and (token := self._session_manager.get_token()):
try:
# Check token is not expired
self._session_manager.authenticator.decode_jwt(token["access_token"])
except jwt.ExpiredSignatureError:
token = self._session_manager.refresh_auth_token()
assert token # This must be present
carr["Authorization"] = f"Bearer {token['access_token']}"

if data:
response = requests.request(method, url, json=data, headers=carr)
else:
Expand Down
Loading

0 comments on commit 97e5275

Please sign in to comment.