Add login to blueapi - #70
noemifrisina wants to merge 18 commits into
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #70 +/- ##
==========================================
- Coverage 94.06% 93.36% -0.70%
==========================================
Files 26 27 +1
Lines 1415 1493 +78
==========================================
+ Hits 1331 1394 +63
- Misses 84 99 +15 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
ZohebShaikh
left a comment
There was a problem hiding this comment.
Apologies for the late review, I had it on my mind on Friday but couldn't get to it.
The PR looks good
| if self.login_dialog.isVisible(): | ||
| self.login_dialog.close() | ||
| tidy_up_logging([self.gui_logger]) | ||
| print("PLEASE LOG OUT FROM BLUEAPI") |
There was a problem hiding this comment.
The cache is getting stored at ~/.cache/blueapi_cache. It would be nice you could get this stored in memory.
This will be a change in blueapi. You will need to create a new In memory Cache.
Then once the UI process terminates the cached tokens are also gone, because my assumption is that people are logging in as a shared user on some i19 beamline computer.
There was a problem hiding this comment.
From our last conversation, I remember that this is not required because everyone logs in to the DLS machine as themselves. And the token is saved in their personal home directory rather than a shared location.
But I'm leaving this to your discretion
… trying to figure out how to decode the token
| self.close() | ||
| except Exception as e: | ||
| log_to_gui(self.logger, "Login failed", level="ERROR") | ||
| self.logger.exception(e) |
There was a problem hiding this comment.
A logout can be added like this
def _on_click_trigger_logout(self):
try:
self.client.client.logout()
self.user_fedid.emit("")
self.close()
except Exception as e:
log_to_gui(self.logger, "Logout failed", level="ERROR")
self.logger.exception(e)
| def create_layout(self): | ||
| layout = QtWidgets.QVBoxLayout() | ||
| lbl = QtWidgets.QLabel("Please log in to run") | ||
| lbl.setFont(QtGui.QFont("Arial", 10)) | ||
| lbl.setAlignment(Qt.AlignmentFlag.AlignCenter) | ||
| self.btn = QtWidgets.QPushButton("LOGIN") | ||
| self.btn.setMaximumHeight(20) | ||
| self.btn.setMaximumWidth(50) | ||
| self.btn.clicked.connect(self._on_click_trigger_login) | ||
| layout.addWidget(lbl) | ||
| layout.addWidget(self.btn) | ||
| self.setLayout(layout) | ||
|
|
||
| def _load_access_token(self) -> str: | ||
| with open(self._token_path, "rb") as fh: | ||
| token = base64.b64decode(fh.read()).decode("utf-8") | ||
| return token | ||
|
|
||
| def _decode_access_token(self) -> dict[str, Any]: | ||
| # NOTE Proper way would be to get the jwt_uri from keycloak | ||
| # But since blueapi will do the verification and we just need to read the fedid | ||
| # this may just be enoug | ||
| token = self._load_access_token() | ||
| payload = jwt.decode(token, options={"verify_signature": False}) | ||
| return payload | ||
|
|
||
| def _update_user_fedid(self) -> str: | ||
| access_token = self._decode_access_token() | ||
| if not access_token: | ||
| raise ValueError("Could not get access token") | ||
| return access_token.get("fedid") # type: ignore |
There was a problem hiding this comment.
The code as is will not work
def _get_session_manager(self) -> SessionManager | None:
try:
return SessionManager.from_cache(
auth_token_path=self.client._blueapi_config.auth_token_path
)
except FileNotFoundError:
# No cached session yet, i.e. user has never logged in
return None
def _update_user_fedid(self) -> str | None:
sm = self._get_session_manager()
if sm is not None and (access_token := sm.get_valid_access_token()) != "":
# Logged In
# Get fedid
return sm.decode_jwt(access_token).get(USER_ID_CLAIM)
def create_layout(self):
layout = QtWidgets.QVBoxLayout()
self.lbl = QtWidgets.QLabel()
self.lbl.setFont(QtGui.QFont("Arial", 10))
self.lbl.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.auth_btn = QtWidgets.QPushButton()
self.auth_btn.setMaximumHeight(20)
layout.addWidget(self.lbl)
layout.addWidget(self.auth_btn, alignment=Qt.AlignmentFlag.AlignCenter)
self.setLayout(layout)
self._refresh_login_state()
def _refresh_login_state(self):
if (fedid := self._update_user_fedid()) is None:
# Not logged in
self.lbl.setText("Please log in to run")
self.auth_btn.setText("LOGIN")
trigger = self._on_click_trigger_login
else:
self.lbl.setText(f"Logged in as {fedid}")
self.auth_btn.setText("LOGOUT")
trigger = self._on_click_trigger_logout
try:
self.auth_btn.clicked.disconnect()
except TypeError:
# Not connected to anything yet
pass
self.auth_btn.clicked.connect(trigger)
def showEvent(self, a0: QtGui.QShowEvent| None):
self._refresh_login_state()
super().showEvent(a0)
I'm refreshing the state in the showEvent which triggered each time the widget is created.
I'm using the session manager defined in bluaepi to do all the decode of token as you are using the same for login.
There was a problem hiding this comment.
The code in it's current state will not work because in the cache I'm not just storing the token.
Closes #64