Skip to content
Snippets Groups Projects
Commit d80c38ef authored by Johana Supíková's avatar Johana Supíková
Browse files

removing tokens

parent ee82e9e2
Branches
No related tags found
No related merge requests found
...@@ -140,3 +140,7 @@ oidc_provider: # REQUIRED for OAuth2/OIDC protection of some endpoints ...@@ -140,3 +140,7 @@ oidc_provider: # REQUIRED for OAuth2/OIDC protection of some endpoints
post_logout_redirect_uris: post_logout_redirect_uris:
- uri1 - uri1
- uri2 - uri2
session_database: # REQUIRED for logout system
connection_string: connection_string
database_name: database_name
consent_collection_name: collection_name
...@@ -2,31 +2,29 @@ import copy ...@@ -2,31 +2,29 @@ import copy
from uuid import uuid4 from uuid import uuid4
import flask import flask
import yaml import yaml
from flask_babel import get_locale, gettext
from flask import Blueprint, request, url_for, render_template, make_response, jsonify, session
from flask_babel import gettext, get_locale
from flask_pyoidc.user_session import UserSession from flask_pyoidc.user_session import UserSession
from perun.proxygui.jwt import JWTService from perun.proxygui.jwt import JWTService
from perun.proxygui.user_manager import UserManager
from flask import ( from flask import (
Blueprint, Blueprint,
request, request,
url_for,
render_template, render_template,
make_response, make_response,
jsonify, jsonify,
session, session,
redirect, redirect,
) )
from flask_babel import gettext
from perun.connector.utils import Logger
from perun.connector.utils import Logger
from perun.proxygui.logout_manager import LogoutManager from perun.proxygui.logout_manager import LogoutManager
from perun.utils.consent_framework.consent_manager import ConsentManager from perun.utils.consent_framework.consent_manager import ConsentManager
from perun.proxygui.user_manager import UserManager from perun.proxygui.user_manager import UserManager
logger = Logger.Logger.get_logger(__name__) logger = Logger.Logger.get_logger(__name__)
def ignore_claims(ignored_claims, claims): def ignore_claims(ignored_claims, claims):
result = dict() result = dict()
...@@ -84,9 +82,10 @@ def construct_gui_blueprint(cfg, auth): ...@@ -84,9 +82,10 @@ def construct_gui_blueprint(cfg, auth):
@gui.route("/logout", methods=["POST", "GET"]) @gui.route("/logout", methods=["POST", "GET"])
def logout(): def logout():
ssp_session_id = request.cookies.get("SimpleSAMLSessionID") ssp_session_id = request.cookies.get("SimpleSAMLSessionID")
user_id = user_manager.get_user_id_by_ssp_session_id(ssp_session_id) # todo - dbs will contain user_id and won't need to be converted in the future
sub = user_manager.get_user_id_by_ssp_session_id(ssp_session_id)
if ssp_session_id is None or user_id is None: if ssp_session_id is None or sub is None:
return render_template("MissingAuth.html") return render_template("MissingAuth.html")
( (
...@@ -97,9 +96,9 @@ def construct_gui_blueprint(cfg, auth): ...@@ -97,9 +96,9 @@ def construct_gui_blueprint(cfg, auth):
) = logout_manager.validate_request_and_extract_params(session, request) ) = logout_manager.validate_request_and_extract_params(session, request)
if valid_request: if valid_request:
user_manager.logout_from_service_op(user_id, rp_sid, client_id) user_manager.logout_from_service_op(sub, rp_sid, client_id)
device_active_clients = user_manager.get_active_client_ids_for_user(user_id) device_active_clients = user_manager.get_active_client_ids_for_user(sub)
session_active_clients = user_manager.get_active_client_ids_for_session( session_active_clients = user_manager.get_active_client_ids_for_session(
request.cookies.get("SimpleSAMLSessionID") request.cookies.get("SimpleSAMLSessionID")
) )
...@@ -121,7 +120,6 @@ def construct_gui_blueprint(cfg, auth): ...@@ -121,7 +120,6 @@ def construct_gui_blueprint(cfg, auth):
device_services = logout_manager.complete_service_names( device_services = logout_manager.complete_service_names(
device_active_clients, rp_names device_active_clients, rp_names
) )
session["logout_params"] = logout_params if logout_params else {} session["logout_params"] = logout_params if logout_params else {}
resp = make_response( resp = make_response(
...@@ -138,20 +136,29 @@ def construct_gui_blueprint(cfg, auth): ...@@ -138,20 +136,29 @@ def construct_gui_blueprint(cfg, auth):
@gui.route("/logout_state", methods=["GET"]) @gui.route("/logout_state", methods=["GET"])
def logout_state(): def logout_state():
ssp_session_id = request.cookies.get("SimpleSAMLSessionID") ssp_session_id = request.cookies.get("SimpleSAMLSessionID")
user_id = user_manager.get_user_id_by_ssp_session_id(ssp_session_id) # todo - dbs will contain user_id and won't need to be converted in the future
sub = user_manager.get_user_id_by_ssp_session_id(ssp_session_id)
if ssp_session_id is None or user_id is None: if ssp_session_id is None or sub is None:
return render_template("MissingAuth.html") return render_template("MissingAuth.html")
include_all_devices = request.args.get("from_devices", False) include_all_devices = request.args.get("from_devices", False)
include_all_devices = include_all_devices in ["True", True, "true"] include_all_devices = include_all_devices in ["True", True, "true"]
if include_all_devices: if include_all_devices:
active_clients = user_manager.get_active_client_ids_for_user(user_id) active_clients = user_manager.get_active_client_ids_for_user(sub)
unique_client_issuer_clients = [] unique_client_issuer_clients = []
for (client_id, sid, issuer) in active_clients: for (client_id, sid, issuer) in active_clients:
found = next(filter(lambda x: x[0] == client_id and x[2] == issuer, unique_client_issuer_clients), None) found = next(
unique_client_issuer_clients.append((client_id, sid, issuer)) if not found else None filter(
lambda x: x[0] == client_id and x[2] == issuer,
unique_client_issuer_clients,
),
None,
)
unique_client_issuer_clients.append(
(client_id, sid, issuer)
) if not found else None
active_clients = unique_client_issuer_clients active_clients = unique_client_issuer_clients
else: else:
active_clients = user_manager.get_active_client_ids_for_session( active_clients = user_manager.get_active_client_ids_for_session(
...@@ -159,14 +166,15 @@ def construct_gui_blueprint(cfg, auth): ...@@ -159,14 +166,15 @@ def construct_gui_blueprint(cfg, auth):
) )
rp_names = user_manager.get_all_rp_names() rp_names = user_manager.get_all_rp_names()
# todo - jazyky brát z config option languages - brát průnik, issuer bude mapa {issuer: pretty_name} # todo - jazyky brát z config option languages - brát průnik,
# issuer bude mapa {issuer: pretty_name}
# todo - pěkná funkce na vyčítání (fallback když je jenom japonská verze atd...) # todo - pěkná funkce na vyčítání (fallback když je jenom japonská verze atd...)
service_configs = logout_manager.fetch_services_configuration() service_configs = logout_manager.fetch_services_configuration()
logout_requests = [ logout_requests = [
logout_manager.prepare_logout_request( logout_manager.prepare_logout_request(
service_configs, service_configs,
client_id, client_id,
user_id, sub,
rp_names.get(client_id, {"en": client_id, "cs": client_id}), rp_names.get(client_id, {"en": client_id, "cs": client_id}),
issuer, issuer,
rp_sid if include_all_devices else None, rp_sid if include_all_devices else None,
...@@ -175,9 +183,15 @@ def construct_gui_blueprint(cfg, auth): ...@@ -175,9 +183,15 @@ def construct_gui_blueprint(cfg, auth):
] ]
session["logout_requests"] = logout_requests session["logout_requests"] = logout_requests
# user_manager.logout(
# user_id = None, session_id=ssp_session_id, include_refresh_tokens=include_all_devices # todo - map current running instance to issuer by app domain?
# ) todo - currently timeouts, user_id != sub sub_issuer = "https://idp2.ics.muni.cz/idp/shibboleth"
user_id = user_manager.sub_to_user_id(sub, sub_issuer)
user_manager.logout(
user_id=user_id,
session_id=ssp_session_id if include_all_devices else None,
include_refresh_tokens=include_all_devices,
)
resp = make_response( resp = make_response(
render_template( render_template(
...@@ -187,7 +201,7 @@ def construct_gui_blueprint(cfg, auth): ...@@ -187,7 +201,7 @@ def construct_gui_blueprint(cfg, auth):
bootstrap_color=COLOR, bootstrap_color=COLOR,
) )
) )
#resp.delete_cookie("SimpleSAMLSessionID") todo - keep commented for testing resp.delete_cookie("SimpleSAMLSessionID")
return resp return resp
@gui.route("/post_logout") @gui.route("/post_logout")
...@@ -241,7 +255,6 @@ def construct_gui_blueprint(cfg, auth): ...@@ -241,7 +255,6 @@ def construct_gui_blueprint(cfg, auth):
result="success" if request_ok else "request invalid", result="success" if request_ok else "request invalid",
) )
@gui.route("/IsTestingSP") @gui.route("/IsTestingSP")
def is_testing_sp(): def is_testing_sp():
return render_template( return render_template(
......
...@@ -43,13 +43,13 @@ ...@@ -43,13 +43,13 @@
<h4>{{ _("You can log out from the following services:") }}</h4> <h4>{{ _("You can log out from the following services:") }}</h4>
<ul> <ul>
{% for service in session_services %} {% for service in session_services %}
<li>{{ service.rp_names.get(lang) }} </li> <li>{{ service.get(lang) }} </li>
{% endfor %} {% endfor %}
</ul> </ul>
<h4>{{ _("You can log out from these services on your other devices:") }}</h4> <h4>{{ _("You can log out from these services on your other devices:") }}</h4>
<ul> <ul>
{% for service in device_services %} {% for service in device_services %}
<li>{{ service.rp_names.get(lang) }} </li> <li>{{ service.get(lang) }} </li>
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
<br/> <br/>
<h1><span>{{ _("Cannot perform action") }}</span></h1> <h1><span>{{ _("Cannot perform action") }}</span></h1>
<br/> <br/>
<p><span>{{ _("Missing cookies - action cannot be performed.") }}</span> {{ service }}</p> <p><span>{{ _("Action cannot be performed. You have either been logged out already or cookies are missing.") }}</span> {{ service }}</p>
</div> </div>
{% endblock %} {% endblock %}
</div> </div>
......
...@@ -79,20 +79,26 @@ class LogoutManager: ...@@ -79,20 +79,26 @@ class LogoutManager:
if logout_token is None: if logout_token is None:
return INVALID_REQUEST return INVALID_REQUEST
if ( if (
"client_id" in logout_token "client_id" in logout_token
and client_id is not None and client_id is not None
and logout_token["client_id"] != client_id and logout_token["client_id"] != client_id
): ):
return INVALID_REQUEST return INVALID_REQUEST
try: try:
# todo - select key by issuer (selected by endpoint url = request.url_root) # todo - select key by issuer (selected by endpoint url = request.url_root)
logout_token = self.jwt_service.verify_jwt(logout_token, self.keystore, self.key_id) logout_token = self.jwt_service.verify_jwt(
logout_token, self.keystore, self.key_id
)
except Exception: except Exception:
return INVALID_REQUEST return INVALID_REQUEST
events = logout_token.get("events") events = logout_token.get("events")
if events is None or events != "https://openid.net/specs/openid-connect-rpinitiated-1_0.html%22": if (
events is None
or events
!= "https://openid.net/specs/openid-connect-rpinitiated-1_0.html%22"
):
return INVALID_REQUEST return INVALID_REQUEST
sid = logout_token.get("sid") sid = logout_token.get("sid")
...@@ -122,7 +128,7 @@ class LogoutManager: ...@@ -122,7 +128,7 @@ class LogoutManager:
try: try:
# todo - select key by issuer (selected by endpoint url = request.url_root) # todo - select key by issuer (selected by endpoint url = request.url_root)
logout_token = self.jwt_service.verify_jwt(id_token_hint, self.keystore, self.key_id) self.jwt_service.verify_jwt(id_token_hint, self.keystore, self.key_id)
except Exception: except Exception:
return INVALID_REQUEST return INVALID_REQUEST
...@@ -206,7 +212,8 @@ class LogoutManager: ...@@ -206,7 +212,8 @@ class LogoutManager:
return False return False
def fetch_services_configuration(self): def fetch_services_configuration(self):
# todo - retrieve configurations for Graph API, metadata and global config properties # todo - retrieve configurations for Graph API,
# metadata and global config properties
# currently only for testing purpose: # currently only for testing purpose:
test_graph_config = "/etc/graph.perun.proxygui.yaml" test_graph_config = "/etc/graph.perun.proxygui.yaml"
with open(test_graph_config, "r", encoding="utf8") as f: with open(test_graph_config, "r", encoding="utf8") as f:
...@@ -245,7 +252,8 @@ class LogoutManager: ...@@ -245,7 +252,8 @@ class LogoutManager:
return LogoutRequest.from_dict(data) return LogoutRequest.from_dict(data)
def complete_service_names(self, clients_data, rp_names): def complete_service_names(self, clients_data, rp_names):
# todo - jazyky brát z config option languages - brát průnik, issuer bude mapa {issuer: pretty_name} # todo - jazyky brát z config option languages - brát průnik,
# issuer bude mapa {issuer: pretty_name}
# todo - pěkná funkce na vyčítání (fallback když je jenom japonská verze atd...) # todo - pěkná funkce na vyčítání (fallback když je jenom japonská verze atd...)
client_ids = {} # client_id: [issuer1, issuer2] client_ids = {} # client_id: [issuer1, issuer2]
......
...@@ -13,7 +13,6 @@ from sqlalchemy import delete, select ...@@ -13,7 +13,6 @@ from sqlalchemy import delete, select
from sqlalchemy.engine import Engine from sqlalchemy.engine import Engine
from sqlalchemy.orm.session import Session from sqlalchemy.orm.session import Session
from perun.utils import Utils
from perun.utils.ConfigStore import ConfigStore from perun.utils.ConfigStore import ConfigStore
from perun.utils.DatabaseService import DatabaseService from perun.utils.DatabaseService import DatabaseService
from perun.utils.EmailService import EmailService from perun.utils.EmailService import EmailService
...@@ -51,12 +50,11 @@ class UserManager: ...@@ -51,12 +50,11 @@ class UserManager:
session_id: str = None, session_id: str = None,
) -> int: ) -> int:
if session_id: if session_id:
# todo - shouldn't be user??? then it's the same and kvstore usage can be replaced with this one?
result = ssp_sessions_collection.delete_many( result = ssp_sessions_collection.delete_many(
{"sub": subject, "key": session_id} {"type": "session", "key": session_id}
) )
elif subject: elif subject:
result = ssp_sessions_collection.delete_many({"sub": subject}) result = ssp_sessions_collection.delete_many({"user": subject})
else: else:
return 0 return 0
...@@ -196,7 +194,6 @@ class UserManager: ...@@ -196,7 +194,6 @@ class UserManager:
if user: if user:
return str(user.id) return str(user.id)
def logout( def logout(
self, self,
user_id: str = None, user_id: str = None,
...@@ -211,7 +208,6 @@ class UserManager: ...@@ -211,7 +208,6 @@ class UserManager:
user id is user id is
provided, all of user's sessions are revoked. provided, all of user's sessions are revoked.
:param user_id: id of user whose sessions are to be revoked :param user_id: id of user whose sessions are to be revoked
:param subject: sub in case it needn't be retrieved from IdM
:param session_id: id of a specific session to revoke :param session_id: id of a specific session to revoke
:param include_refresh_tokens: specifies whether refresh tokens :param include_refresh_tokens: specifies whether refresh tokens
should be should be
...@@ -252,18 +248,20 @@ class UserManager: ...@@ -252,18 +248,20 @@ class UserManager:
self._revoke_satosa_grants( self._revoke_satosa_grants(
satosa_sessions_collection, subject, session_id, client_id satosa_sessions_collection, subject, session_id, client_id
) )
# todo - add more op token removal options? (remove single ssp sessionIndex entry?) # todo - add more op token removal options? keep skipping mitre?
# (at least remove single ssp sessionIndex entry?)
def get_active_client_ids_for_user(self, user_id: str) -> set[str]: def get_active_client_ids_for_user(self, sub: str) -> set[str]:
""" """
Returns list of unique client ids retrieved from active user's Returns list of unique client ids retrieved from active user's
sessions. sessions.
:param user_id: user, whose sessions are retrieved :param user_id: user, whose sessions are retrieved
:return: list of client ids :return: list of client ids
""" """
subject = self.extract_user_attribute(self._SUBJECT_ATTRIBUTE, int(user_id)) # todo -- when user_id is stored in SSP db, this conversion will be needed
ssp_clients = self._get_ssp_entity_ids_by_user(subject) # subject = self.extract_user_attribute(self._SUBJECT_ATTRIBUTE, int(user_id))
satosa_clients = self._get_satosa_client_ids_by_user(subject) ssp_clients = self._get_ssp_entity_ids_by_user(sub)
satosa_clients = self._get_satosa_client_ids_by_user(sub)
# mitre_clients = self._get_mitre_client_ids_by_user(user_id) # mitre_clients = self._get_mitre_client_ids_by_user(user_id)
return ssp_clients + satosa_clients return ssp_clients + satosa_clients
...@@ -432,7 +430,6 @@ class UserManager: ...@@ -432,7 +430,6 @@ class UserManager:
def forward_mfa_reset_request(self, requester_email: str) -> None: def forward_mfa_reset_request(self, requester_email: str) -> None:
self.email_service.send_mfa_reset_request(requester_email) self.email_service.send_mfa_reset_request(requester_email)
def _get_issuer_from_id_token(self, id_token): def _get_issuer_from_id_token(self, id_token):
# todo - key will be per issuer? # todo - key will be per issuer?
# claims = json.loads(verify_jwt(id_token, self._KEYSTORE, self._KEY_ID)) # claims = json.loads(verify_jwt(id_token, self._KEYSTORE, self._KEY_ID))
...@@ -441,7 +438,8 @@ class UserManager: ...@@ -441,7 +438,8 @@ class UserManager:
def get_all_rp_names(self): def get_all_rp_names(self):
""" """
Returns structure of {service_name: {'cs': cs_label, 'en': en_label} from attribute Returns structure of {service_name: {'cs': cs_label, 'en': en_label}
from Perun mfaCategories attribute
""" """
result = {} result = {}
names = self._ADAPTERS_MANAGER.get_entityless_attribute( names = self._ADAPTERS_MANAGER.get_entityless_attribute(
......
...@@ -25,7 +25,7 @@ class LogoutRequest: ...@@ -25,7 +25,7 @@ class LogoutRequest:
"op_id": self.op_id, "op_id": self.op_id,
"rp_names": self.rp_names, "rp_names": self.rp_names,
"logout_type": self.logout_type, "logout_type": self.logout_type,
"iframe_src": self.iframe_src "iframe_src": self.iframe_src,
} }
@staticmethod @staticmethod
......
import flask import flask
from perun.utils.logout_requests.LogoutRequest import LogoutRequest from perun.utils.logout_requests.LogoutRequest import LogoutRequest
# from saml2.client import Saml2Client # from saml2.client import Saml2Client
# from saml2 import saml # from saml2 import saml
class SamlLogoutRequest(LogoutRequest): class SamlLogoutRequest(LogoutRequest):
""" Iframe should connect to th SP directly with prepared SAML request and contain our endpoint as a callback url. """ """Iframe should connect to th SP directly with prepared SAML request and contain our endpoint as a callback url."""
def __init__(self, op_id, client_id, rp_names): def __init__(self, op_id, client_id, rp_names):
LogoutRequest.__init__(self, op_id, client_id, rp_names, "SAML_LOGOUT") LogoutRequest.__init__(self, op_id, client_id, rp_names, "SAML_LOGOUT")
...@@ -18,4 +21,4 @@ class SamlLogoutRequest(LogoutRequest): ...@@ -18,4 +21,4 @@ class SamlLogoutRequest(LogoutRequest):
def prepare_saml_request(self, sub, sid): def prepare_saml_request(self, sub, sid):
# todo - use pysaml2? # todo - use pysaml2?
return None return None
\ No newline at end of file
...@@ -26,7 +26,7 @@ setup( ...@@ -26,7 +26,7 @@ setup(
"perun.connector~=3.7", "perun.connector~=3.7",
"python-smail~=0.9.0", "python-smail~=0.9.0",
"SQLAlchemy~=2.0.19", "SQLAlchemy~=2.0.19",
"pymongo~=4.4.1", "pymongo~=3.13.0", # downgrade pymongo for Flask (internal) Sessions to work
"validators~=0.22.0", "validators~=0.22.0",
"idpyoidc~=2.0.0", "idpyoidc~=2.0.0",
"python-dateutil~=2.8.2", "python-dateutil~=2.8.2",
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment