Skip to content

Commit

Permalink
[MRG] Merge pull request #592 from dfir-iris/hotfix_2.4.13
Browse files Browse the repository at this point in the history
Hotfix 2.4.13
  • Loading branch information
whikernel authored Sep 23, 2024
2 parents 7123a2a + d544c60 commit d74097c
Show file tree
Hide file tree
Showing 21 changed files with 295 additions and 44 deletions.
2 changes: 1 addition & 1 deletion .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 2.4.12
current_version = 2.4.13
commit = True
tag = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(-(?P<release>.*)-(?P<build>\d+))?
Expand Down
11 changes: 11 additions & 0 deletions .env.model
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,14 @@ IRIS_AUTHENTICATION_TYPE=local

# -- LISTENING PORT
INTERFACE_HTTPS_PORT=443

# -- FOR OIDC AUTHENTICATION
IRIS_AUTHENTICATION_TYPE=oidc
OIDC_ISSUER_URL=
OIDC_CLIENT_ID=
OIDC_CLIENT_SECRET=
# endpoints only required if provider doesn't support metadata discovery
OIDC_AUTH_ENDPOINT=
OIDC_TOKEN_ENDPOINT=
# optional to include logout from oidc provider
OIDC_END_SESSION_ENDPOINT=
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<p align="center">
Incident Response Investigation System
<br>
<i>Current Version v2.4.12</i>
<i>Current Version v2.4.13</i>
<br>
<a href="https://v200.beta.dfir-iris.org">Online Demonstration</a>
</p>
Expand Down Expand Up @@ -52,13 +52,13 @@ git clone https://github.com/dfir-iris/iris-web.git
cd iris-web

# Checkout to the last tagged version
git checkout v2.4.12
git checkout v2.4.13

# Copy the environment file
cp .env.model .env

# Build the dockers
docker compose build
# Pull the dockers
docker compose pull

# Run IRIS
docker compose up
Expand Down
8 changes: 4 additions & 4 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,25 +25,25 @@ services:
extends:
file: docker-compose.base.yml
service: db
image: ${DB_IMAGE_NAME:-ghcr.io/dfir-iris/iriswebapp_db}:${DB_IMAGE_TAG:-v2.4.12}
image: ${DB_IMAGE_NAME:-ghcr.io/dfir-iris/iriswebapp_db}:${DB_IMAGE_TAG:-v2.4.13}

app:
extends:
file: docker-compose.base.yml
service: app
image: ${APP_IMAGE_NAME:-ghcr.io/dfir-iris/iriswebapp_app}:${APP_IMAGE_TAG:-v2.4.12}
image: ${APP_IMAGE_NAME:-ghcr.io/dfir-iris/iriswebapp_app}:${APP_IMAGE_TAG:-v2.4.13}

worker:
extends:
file: docker-compose.base.yml
service: worker
image: ${APP_IMAGE_NAME:-ghcr.io/dfir-iris/iriswebapp_app}:${APP_IMAGE_TAG:-v2.4.12}
image: ${APP_IMAGE_NAME:-ghcr.io/dfir-iris/iriswebapp_app}:${APP_IMAGE_TAG:-v2.4.13}

nginx:
extends:
file: docker-compose.base.yml
service: nginx
image: ${NGINX_IMAGE_NAME:-ghcr.io/dfir-iris/iriswebapp_nginx}:${NGINX_IMAGE_TAG:-v2.4.12}
image: ${NGINX_IMAGE_NAME:-ghcr.io/dfir-iris/iriswebapp_nginx}:${NGINX_IMAGE_TAG:-v2.4.13}

volumes:
iris-downloads:
Expand Down
4 changes: 4 additions & 0 deletions source/app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@

from app.flask_dropzone import Dropzone
from app.iris_engine.tasker.celery import make_celery
from app.iris_engine.access_control.oidc_handler import get_oidc_client


class ReverseProxied(object):
Expand Down Expand Up @@ -128,6 +129,9 @@ def ac_current_user_has_manage_perms():
alerts_namespace = AlertsNamespace('/alerts')
socket_io.on_namespace(alerts_namespace)

oidc_client = None
if app.config.get('AUTHENTICATION_TYPE') == "oidc":
oidc_client = get_oidc_client(app)

@app.teardown_appcontext
def shutdown_session(exception=None):
Expand Down
2 changes: 1 addition & 1 deletion source/app/blueprints/alerts/alerts_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,7 @@ def alerts_batch_delete_route() -> Response:
if not success:
return response_error(logs)

alert = call_modules_hook('on_postload_alert_delete', data={"alert_ids": {alert_ids}})
alert = call_modules_hook('on_postload_alert_delete', data={"alert_ids": alert_ids})

track_activity(f"deleted alerts #{','.join(str(alert_id) for alert_id in alert_ids)}", ctx_less=True)

Expand Down
20 changes: 20 additions & 0 deletions source/app/blueprints/dashboard/dashboard_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@

from app import app
from app import db
from app import oidc_client
from app.datamgmt.dashboard.dashboard_db import get_global_task, list_user_cases, list_user_reviews
from app.datamgmt.dashboard.dashboard_db import get_tasks_status
from app.datamgmt.dashboard.dashboard_db import list_global_tasks
Expand All @@ -53,6 +54,9 @@
from app.util import not_authenticated_redirection_url
from app.util import response_error
from app.util import response_success
from app.util import is_authentication_oidc

from oic.oauth2.exception import GrantError

# CONTENT ------------------------------------------------
dashboard_blueprint = Blueprint(
Expand All @@ -74,6 +78,22 @@ def logout():
current_user.ctx_human_case = session['current_case']['case_name']
db.session.commit()

if is_authentication_oidc():
if oidc_client.provider_info.get("end_session_endpoint"):
try:
logout_request = oidc_client.construct_EndSessionRequest(state=session["oidc_state"])
logout_url = logout_request.request(oidc_client.provider_info["end_session_endpoint"])
track_activity("user '{}' has been logged-out".format(current_user.user), ctx_less=True, display_in_ui=False)
logout_user()
session.clear()
return redirect(logout_url)
except GrantError:
track_activity(
f"no oidc session found for user '{current_user.user}', skipping oidc provider logout and continuing to logout local user",
ctx_less=True,
display_in_ui=False
)

track_activity("user '{}' has been logged-out".format(current_user.user), ctx_less=True, display_in_ui=False)
logout_user()
session.clear()
Expand Down
106 changes: 101 additions & 5 deletions source/app/blueprints/login/login_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@
import pyotp
import qrcode
from urllib.parse import urlsplit
import random
import string

from oic import rndstr
from oic.oic.message import AuthorizationResponse

# IMPORTS ------------------------------------------------

Expand All @@ -37,16 +42,18 @@
from app import app
from app import bc
from app import db
from app import oidc_client
from app.datamgmt.manage.manage_srv_settings_db import get_server_settings_as_dict

from app.forms import LoginForm, MFASetupForm
from app.iris_engine.access_control.ldap_handler import ldap_authenticate
from app.iris_engine.access_control.utils import ac_get_effective_permissions_of_user
from app.iris_engine.utils.tracker import track_activity
from app.models.cases import Cases
from app.util import is_authentication_ldap, regenerate_session
from app.datamgmt.manage.manage_users_db import get_active_user_by_login

from app.util import is_authentication_ldap, response_error
from app.util import is_authentication_oidc
from app.datamgmt.manage.manage_users_db import get_active_user_by_login, get_user
from app.datamgmt.manage.manage_users_db import create_user

login_blueprint = Blueprint(
'login',
Expand All @@ -70,9 +77,10 @@ def _render_template_login(form, msg):
organisation_name = app.config.get('ORGANISATION_NAME')
login_banner = app.config.get('LOGIN_BANNER_TEXT')
ptfm_contact = app.config.get('LOGIN_PTFM_CONTACT')
auth_type = app.config.get('AUTHENTICATION_TYPE')

return render_template('login.html', form=form, msg=msg, organisation_name=organisation_name,
login_banner=login_banner, ptfm_contact=ptfm_contact)
login_banner=login_banner, ptfm_contact=ptfm_contact, auth_type=auth_type)


def _validate_local_login(username, password):
Expand Down Expand Up @@ -135,18 +143,22 @@ def _authenticate_password(form, username, password):

# CONTENT ------------------------------------------------
# Authenticate user
if app.config.get("AUTHENTICATION_TYPE") in ["local", "ldap"]:
if app.config.get("AUTHENTICATION_TYPE") in ["local", "ldap", "oidc"]:
@login_blueprint.route('/login', methods=['GET', 'POST'])
def login():
#session.permanent = True

if current_user.is_authenticated:
return redirect(url_for('index.index'))

if is_authentication_oidc() and app.config.get('AUTHENTICATION_LOCAL_FALLBACK') is False:
return redirect(url_for('login.oidc_login'))

form = LoginForm(request.form)

# check if both http method is POST and form is valid on submit
if not form.is_submitted() and not form.validate():

return _render_template_login(form, None)

# assign form data to variables
Expand All @@ -158,6 +170,90 @@ def login():

return _authenticate_password(form, username, password)

if is_authentication_oidc():
@login_blueprint.route('/oidc-login')
def oidc_login():
if current_user.is_authenticated:
return redirect(url_for('index.index'))

session["oidc_state"] = rndstr()
session["oidc_nonce"] = rndstr()

args = {
"client_id": oidc_client.client_id,
"response_type": "code",
"scope": app.config.get("OIDC_SCOPES"),
"nonce": session["oidc_nonce"],
"redirect_uri": url_for("login.oidc_authorise", _external=True),
"state": session["oidc_state"]
}

auth_req = oidc_client.construct_AuthorizationRequest(request_args=args)
login_url = auth_req.request(oidc_client.authorization_endpoint)

return redirect(login_url)

if is_authentication_oidc():
@login_blueprint.route('/oidc-authorize')
def oidc_authorise():
auth_resp = oidc_client.parse_response(AuthorizationResponse, info=request.args,
sformat="dict")

if auth_resp["state"] != session["oidc_state"]:
track_activity(
f"OIDC session state '{auth_resp['state']}' does not match authorization state '{session['oidc_state']}'",
ctx_less=True,
display_in_ui=False,
)
return redirect(url_for("login.login"))

args = {
"code": auth_resp["code"],
}

access_token_resp = oidc_client.do_access_token_request(state=auth_resp["state"], request_args=args)

# not all providers set email by default, use preferred_username where it's missing
# Use the mapping from the configuration or default to email or preferred_username if not set
email_field = app.config.get("OIDC_MAPPING_EMAIL")
username_field = app.config.get("OIDC_MAPPING_USERNAME")

user_login = access_token_resp['id_token'].get(email_field) or access_token_resp['id_token'].get(username_field)
user_name = access_token_resp['id_token'].get(email_field) or access_token_resp['id_token'].get(username_field)

user = get_user(user_login, 'user')

if not user:
if app.config.get("AUTHENTICATION_CREATE_USER_IF_NOT_EXISTS") is False:
track_activity(
f"OIDC user {user_login} not found in database",
ctx_less=True,
display_in_ui=False,
)
return response_error("User not found in IRIS", 404)

track_activity(
f"Creating OIDC user {user_login} in database",
ctx_less=True,
display_in_ui=False,
)

# generate random password
password = ''.join(random.choices(string.printable[:-6], k=16))

user = create_user(
user_name=user_name,
user_login=user_login,
user_email=user_login,
user_password=bc.generate_password_hash(password.encode('utf8')).decode('utf8'),
user_active=True,
user_is_service_account=False
)

if user and not user.active:
return response_error("User not active in IRIS", 403)

return wrap_login_user(user)

def wrap_login_user(user):

Expand Down
3 changes: 3 additions & 0 deletions source/app/blueprints/login/templates/login.html
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ <h3 class="login__form_title">Sign In</h3>
{% endif %}
<div class="login__form_field_container">
<button type="submit" class="btn btn-primary login__submit_button">Sign In</button>
{% if auth_type == 'oidc' %}
<a href="/oidc-login" class="btn float-right btn-light login__submit_button">Use SSO</a>
{% endif %}
</div>
</div>
</form>
Expand Down
6 changes: 4 additions & 2 deletions source/app/blueprints/manage/manage_customers_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
from app.datamgmt.exceptions.ElementExceptions import ElementInUseException
from app.datamgmt.exceptions.ElementExceptions import ElementNotFoundException
from app.datamgmt.manage.manage_attribute_db import get_default_custom_attributes
from app.datamgmt.manage.manage_users_db import add_user_to_customer
from app.forms import AddCustomerForm
from app.forms import ContactForm
from app.iris_engine.utils.tracker import track_activity
Expand Down Expand Up @@ -340,7 +341,6 @@ def view_customers(client_id):

@manage_customers_blueprint.route('/manage/customers/add/modal', methods=['GET'])
@ac_requires(Permissions.customers_read, no_cid_required=True)
@ac_requires_client_access()
def add_customers_modal(caseid, url_redir):
if url_redir:
return redirect(url_for('manage_customers.manage_customers', cid=caseid))
Expand All @@ -351,7 +351,6 @@ def add_customers_modal(caseid, url_redir):

@manage_customers_blueprint.route('/manage/customers/add', methods=['POST'])
@ac_api_requires(Permissions.customers_write)
@ac_api_requires_client_access()
def add_customers():
if not request.is_json:
return response_error("Invalid request")
Expand All @@ -366,6 +365,9 @@ def add_customers():

track_activity(f"Added customer {client.name}", ctx_less=True)

# Associate the created customer with the current user
add_user_to_customer(current_user.id, client.client_id)

# Return the customer
client_schema = CustomerSchema()
return response_success("Added successfully", data=client_schema.dump(client))
Expand Down
Loading

0 comments on commit d74097c

Please sign in to comment.