Skip to content

Commit

Permalink
[SSO] - Handling sso logout
Browse files Browse the repository at this point in the history
  • Loading branch information
marcus-oscarsson committed Oct 1, 2024
1 parent 56b9e01 commit f1f7f69
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 20 deletions.
1 change: 1 addition & 0 deletions demo/mxcube-web/server.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ server:
sso:
ISSUER: https://websso.esrf.fr/realms/ESRF/
LOGOUT_URI: https://websso.esrf.fr/auth/realms/ESRF/protocol/openid-connect/logout
TOKEN_INFO_URI: https://websso.esrf.fr/auth/realms/ESRF/protocol/openid-connect/token
CLIENT_SECRET: 95nOugpRxwF3ttXxYnXFiK6bou5wtSP1
CLIENT_ID: mxcube
SCOPE: openid email profile
Expand Down
45 changes: 30 additions & 15 deletions mxcubeweb/core/components/user/usermanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import uuid
import datetime
import requests
import json

import flask
import flask_security
Expand All @@ -24,18 +25,11 @@ def __init__(self, app, config):
super().__init__(app, config)
self.oauth_client = OAuth(app=app.server.flask)

self.oauth_issuer = self.app.CONFIG.sso.ISSUER
self.oauth_logout_url = self.app.CONFIG.sso.LOGOUT_URI
self.oauth_client_secret = self.app.CONFIG.sso.CLIENT_SECRET
self.oauth_client_id = self.app.CONFIG.sso.CLIENT_ID

self.oauth_client.register(
name="keycloak",
client_id=self.oauth_client_id,
client_secret=self.oauth_client_secret,
server_metadata_url=(
"https://websso.esrf.fr/realms/ESRF/.well-known/openid-configuration"
),
client_id=self.app.CONFIG.sso.CLIENT_ID,
client_secret=self.app.CONFIG.sso.CLIENT_SECRET,
server_metadata_url=self.app.CONFIG.sso.META_DATA_URI,
client_kwargs={
"scope": "openid email profile",
"code_challenge_method": "S256", # enable PKCE
Expand Down Expand Up @@ -172,6 +166,25 @@ def sso_validate(self) -> str:
else:
self.login(username, token, sso_data=token_response)

def sso_token_expired(self) -> bool:
res = json.loads(
requests.post(
self.app.CONFIG.sso.TOKEN_INFO_URI,
headers={"Authorization": "Bearer %s" % current_user.token},
data={
"grant_type": "refresh_token",
"refresh_token": current_user.refresh_token,
},
).json()
)

return "access_token" not in res

def handle_sso_logout(self):
if current_user.is_anonymous:
if self.sso_token_expired():
self.signout()

def login(self, login_id: str, password: str, sso_data: dict = {}):
try:
sessionManager: LimsSessionManager = self._login(login_id, password)
Expand Down Expand Up @@ -334,13 +347,15 @@ def db_create_user(
nickname=user,
session_id=sid,
selected_proposal=selected_proposal,
limsdata=lims_data.json(), # json.dumps(lims_data),
limsdata=lims_data.json(),
refresh_token=sso_data.get("refresh_token", str(uuid.uuid4())),
token=sso_data.get("token", str(uuid.uuid4())),
roles=self._get_configured_roles(user),
)
else:
_u.limsdata = lims_data.json() # json.dumps(lims_data)
_u.refresh_token = sso_data.get("refresh_token", "")
_u.refresh_token = sso_data.get("refresh_token", str(uuid.uuid4()))
_u.token = sso_data.get("token", str(uuid.uuid4()))
user_datastore.append_roles(_u, self._get_configured_roles(user))

self.app.server.user_datastore.commit()
Expand Down Expand Up @@ -455,10 +470,10 @@ def _login(self, login_id: str, password: str) -> LimsSessionManager:

def _signout(self):
requests.post(
self.oauth_logout_url,
self.app.CONFIG.sso.LOGOUT_URI,
data={
"client_id": self.oauth_client_id,
"client_secret": self.oauth_client_secret,
"client_id": self.app.CONFIG.sso.CLIENT_ID,
"client_secret": self.app.CONFIG.sso.CLIENT_SECRET,
"refresh_token": current_user.refresh_token,
},
)
Expand Down
5 changes: 4 additions & 1 deletion mxcubeweb/core/models/configmodels.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ class SSOConfigModel(BaseModel):
LOGOUT_URI: str = Field("", description="OpenIDConnect / OAuth logout URI")
CLIENT_SECRET: str = Field("", description="OpenIDConnect / OAuth client secret")
CLIENT_ID: str = Field("", description="OpenIDConnect / OAuth client id")
META_DATA_URI: str = Field(
"", description="OpenIDConnect / OAuth .well-known configuration"
)
SCOPE: str = Field(
"openid email profile", description="OpenIDConnect / OAuth scope"
)
Expand Down Expand Up @@ -139,4 +142,4 @@ class MXCUBEAppConfigModel(BaseModel):
class AppConfigModel(BaseModel):
server: FlaskConfigModel
mxcube: MXCUBEAppConfigModel
sso: SSOConfigModel
sso: Optional[SSOConfigModel]
1 change: 1 addition & 0 deletions mxcubeweb/core/models/usermodels.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ class User(Base, UserMixin):
limsdata = Column(JSON, unique=False)
last_request_timestamp = Column(DateTime())
refresh_token = Column(String(255), unique=True)
token = Column(String(255), unique=True)
roles = relationship(
"Role",
secondary="roles_users",
Expand Down
15 changes: 11 additions & 4 deletions mxcubeweb/routes/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ def login():
try:
app.usermanager.login(login_id, password)
except BaseException as ex:
import sys, traceback
import sys
import traceback

traceback.print_exc(file=sys.stdout)
msg = "[LOGIN] User %s could not login" % login_id
Expand All @@ -55,8 +56,14 @@ def login():

return res

@bp.route("/sso_post_logout", methods=["GET"])
@server.restrict
def ssosignout():
app.usermanager.signout()
return redirect("/")

@bp.route("/ssologin", methods=["GET"])
def ssosingin():
def ssosignin():
redirect_uri = url_for("login.auth", _external=True)
response = app.usermanager.oauth_client.keycloak.authorize_redirect(
redirect_uri
Expand Down Expand Up @@ -103,12 +110,11 @@ def login_info():
200: Error, could not log in, {"loggedIn": False}
"""
try:

res = app.usermanager.login_info()

response = jsonify(res)
session.permanent = True
except Exception as e:
except Exception:
response = make_response(jsonify({"loggedIn": False}), 200)

return response
Expand All @@ -127,6 +133,7 @@ def refresh_session():
# Since default value of `SESSION_REFRESH_EACH_REQUEST` config setting is `True`
# there is no need to do anything to refresh the session.
app.usermanager.update_active_users()
app.usermanager.handle_sso_logout()
return make_response("", 200)

return bp

0 comments on commit f1f7f69

Please sign in to comment.