Skip to content
Snippets Groups Projects
Unverified Commit 0017a9d3 authored by Marek Hávka's avatar Marek Hávka
Browse files

feat: heuristic page with oidc

parent a8e83827
No related branches found
No related tags found
No related merge requests found
Pipeline #327539 passed
......@@ -228,3 +228,29 @@ that the input data is passed in binary form as `.tar` file in the request.
- `HTTP OK [200]` indicating a successful operation, the body of the response includes either the ban information as a
JSON if it exists or an empty JSON `{}` if a ban with given ID doesn't exist
### Heuristic page
Provides information about user authentication events gathered by AuthEventLogging microservice, to confirm theirs identity.
<br></br>
**Endpoint:** `/HeuristicGetID`
**Description:** Used to gather ID of searched user
**Result:**
- `HHTP OK [200]` indicating successfull load of search page
<br></br>
**Endpoint:** `/GetHeuristic`
**Method:** `GET`
**Description:** Used for showing gathered information about past athentications of user, and showing statistics based on that data.
**Input arguments:** ID of searched user
**Result:**
- `HHTP OK [200]` indicating successfull load of show page
......@@ -124,6 +124,14 @@ consent_database: # REQUIRED
database_name: database_name
consent_collection_name: collection_name
ticket_collection_name: collection_name
oauth2_provider: # REQUIRED data for a shared Oauth2 configuration for protection of some endpoints
issuer: "https://id.muni.cz/"
name: provider_name
client_id: id
client_secret: secret
scopes:
- openid
- perun_consent_api
jwt_nonce_database: # REQUIRED
connection_string: connection_string
database_name: database_name
......@@ -140,3 +148,5 @@ oidc_provider: # REQUIRED for OAuth2/OIDC protection of some endpoints
post_logout_redirect_uris:
- uri1
- uri2
auth_event_logging: # REQUIRED
logging_db: postgresql+psycopg2://user:password@hostname/database_name
from satosacontrib.perun.utils.AuthEventLoggingDbModels import (
AuthEventLoggingTable,
UserAgentTable,
RequestedAcrsTable,
)
from sqlalchemy import create_engine, MetaData, distinct
from user_agents import parse
class AuthEventLoggingQuerries:
def __init__(self, cfg):
self.logging_db = cfg["auth_event_logging"]["logging_db"]
def get_last_5_cities(self, user):
engine = create_engine(self.logging_db)
with engine.connect() as cnxn:
meta_data = MetaData(bind=engine)
MetaData.reflect(meta_data)
auth_event_logging = AuthEventLoggingTable().__table__
querry = (
auth_event_logging.select(
[distinct(auth_event_logging.c.geolocation_city)]
)
.order_by(auth_event_logging.c.day.desc())
.where(auth_event_logging.columns.user == user)
.limit(5)
)
response = cnxn.execute(querry).fetchall()
result = [r._asdict() for r in response]
cities = []
for item in result:
cities.append(item["geolocation_city"])
return cities
def get_last_100_times(self, user):
engine = create_engine(self.logging_db)
with engine.connect() as cnxn:
meta_data = MetaData(bind=engine)
MetaData.reflect(meta_data)
auth_event_logging = AuthEventLoggingTable().__table__
querry = (
auth_event_logging.select(auth_event_logging.c.day)
.order_by(auth_event_logging.c.day.desc())
.where(auth_event_logging.c.user == user)
.limit(100)
)
response = cnxn.execute(querry).fetchall()
result = [r._asdict() for r in response]
times = {}
# Dividing timestamps into dict by half-hours -> {'7:30': 2, '10:00': 1, ...}
for item in result:
hour = str(item["day"].hour)
minutes = item["day"].minute
if minutes >= 30:
minutes = str(3)
else:
minutes = str(0)
if hour + ":" + minutes + "0" not in times:
times[hour + ":" + minutes + "0"] = 1
else:
times[hour + ":" + minutes + "0"] += 1
return times
def get_ids_from_foreign_table(self, cnxn, user, auth_table, requested_col, limit):
querry = (
auth_table.select(requested_col)
.where(auth_table.c.user == user)
.limit(limit)
)
return cnxn.execute(querry).fetchall()
def get_unique_user_agents(self, user):
engine = create_engine(self.logging_db)
with engine.connect() as cnxn:
meta_data = MetaData(bind=engine)
MetaData.reflect(meta_data)
auth_event_logging = AuthEventLoggingTable().__table__
user_agent = UserAgentTable().__table__
ids = self.get_ids_from_foreign_table(
cnxn, user, auth_event_logging, auth_event_logging.c.user_agent_id, 200
)
querry = user_agent.select([distinct(user_agent.c.value)]).where(
user_agent.c.id in ids
)
response = cnxn.execute(querry).fetchall()
result = [r._asdict() for r in response]
agents = []
for item in result:
agents.append(parse(item["value"]))
return agents
def get_unique_arcs(self, user):
engine = create_engine(self.logging_db)
with engine.connect() as cnxn:
meta_data = MetaData(bind=engine)
MetaData.reflect(meta_data)
auth_event_logging = AuthEventLoggingTable().__table__
req_arcs = RequestedAcrsTable().__table__
ids = self.get_ids_from_foreign_table(
cnxn,
user,
auth_event_logging,
auth_event_logging.c.requested_arcs_id,
100,
)
querry = req_arcs.select([distinct(req_arcs.c.value)]).where(
req_arcs.c.id in ids
)
response = cnxn.execute(querry).fetchall()
result = [r._asdict() for r in response]
arcs = []
for item in result:
arcs.append(item["value"])
return arcs
......@@ -24,6 +24,7 @@ from perun.proxygui.oauth import (
)
from perun.utils.CustomRPHandler import CustomRPHandler
PROXYGUI_CFG = "perun.proxygui.yaml"
BACKCHANNEL_LOGOUT_CFG = "backchannel-logout.yaml"
......@@ -44,6 +45,7 @@ def get_config_path(filename: str, required=True) -> Optional[str]:
def get_config(filename=PROXYGUI_CFG, required=True) -> dict:
cfg_path = get_config_path(filename, required)
print(cfg_path)
if not cfg_path:
return {}
with open(
......@@ -134,6 +136,7 @@ def get_flask_app(cfg):
app.register_blueprint(construct_ban_api_blueprint(cfg))
app.register_blueprint(construct_kerberos_auth_api_blueprint(cfg))
# to avoid breaking change
if "consent" in cfg:
oauth_cfg = cfg["oidc_provider"]
configure_resource_protector(oauth_cfg)
......
......@@ -10,6 +10,7 @@ from flask_pyoidc.user_session import UserSession
from perun.proxygui.jwt import JWTService
from perun.proxygui.user_manager import UserManager
from perun.proxygui.api.heuristic_api import AuthEventLoggingQuerries
from perun.utils.consent_framework.consent_manager import ConsentManager
......@@ -23,11 +24,13 @@ def ignore_claims(ignored_claims, claims):
return result
# def construct_gui_blueprint(cfg, auth):
def construct_gui_blueprint(cfg, auth):
gui = Blueprint("gui", __name__, template_folder="templates")
consent_db_manager = ConsentManager(cfg)
user_manager = UserManager(cfg)
jwt_service = JWTService(cfg)
auth_event = AuthEventLoggingQuerries(cfg)
REDIRECT_URL = cfg["redirect_url"]
COLOR = cfg["bootstrap_color"]
......@@ -157,4 +160,36 @@ def construct_gui_blueprint(cfg, auth):
send_mfa_reset_emails=url_for("gui.send_mfa_reset_emails"),
)
@auth.oidc_auth(OIDC_CFG["provider_name"])
@gui.route("/HeuristicGetID")
def heuristic_get_id():
return render_template(
"HeuristicData.html",
redirect_url=REDIRECT_URL,
bootstrap_color=COLOR,
selected=False,
)
@auth.oidc_auth(OIDC_CFG["provider_name"])
@gui.route("/GetHeuristic", methods=["GET"])
def get_heuristic():
user_id = request.args.get("user")
"""
Database selects from auth_event_logging_microservice
e.g.
data = get_data(user_id)
"""
return render_template(
"HeuristicData.html",
redirect_url=REDIRECT_URL,
bootstrap_color=COLOR,
selected=True,
user=user_id,
last_5_cities=auth_event.get_last_5_cities(user_id),
last_100_times=auth_event.get_last_100_times(user_id),
user_agents=auth_event.get_unique_user_agents(user_id),
arcs=auth_event.get_unique_arcs(user_id),
)
return gui
{% extends 'base.html' %}
{% block contentwrapper %}
<div class="window {% if cfg.css_framework == 'MUNI' %}framework_muni{% else %}framework_bootstrap5 bg-light{% endif %}">
<div id="content">
<div class="wrap{% if not cfg.css_framework == 'MUNI' %} container{% endif %}">
{% block content %}
{% if selected %}
<div class="content">
<br/>
<h3><span>{{ _("User ID: ") }}{{ user }}</span></h3>
<div class="grid">
<div class="grid__cell size--l--5-12 text-center">
<h5>Last 5 cities connected from:</h5>
{% for city in last_5_cities %}
{{ city }}<br/>
{% endfor %}
</div>
<div class="grid__cell size--l--5-12 text-center">
<h5>Time activity</h5>
{{ last_100_times }}
</div>
</div>
<div class="grid text-center">
<div class="grid__cell size--l--5-12 text-center">
<h5>User agents</h5>
{% for agent in user_agents %}
{{ agent }}<br/>
{% endfor %}
</div>
<div class="grid__cell size--l--5-12 text-center">
<h5>Unique requested arcs</h5>
{% for arc in arcs %}
{{ arc }}<br/>
{% endfor %}
</div>
</div>
</div>
{% else %}
<div class="content">
<br/>
<h3><span>{{ _("Specify an ID of user to gather data:") }}</span></h3>
<form action="/GetHeuristic" method="get">
<input type="number" id="userID" name="user" min="1" required>
<input type="submit" value="Submit" name="submit"/>
</form>
</div>
{% endif %}
{% endblock %}
</div>
</div>
</div>
{% endblock %}
\ No newline at end of file
[metadata]
version = 3.0.3
license_files = LICENSE
......@@ -29,6 +29,8 @@ setup(
"pymongo~=4.4.1",
"validators~=0.22.0",
"idpyoidc~=2.0.0",
"satosacontrib.perun~=4.1",
"user-agents~=2.2.0",
],
extras_require={
"kerberos": [
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment