Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
1fafef2
implementation of BluePrint, fixed local dependencies of pytest "test…
JanEisermann Mar 13, 2026
f19cf8d
implementation of BluePrint, fixed local dependencies of pytest "test…
JanEisermann Mar 13, 2026
7ed2570
moved file_exists to mslib.utils, moved the constants to the beginnin…
JanEisermann Mar 13, 2026
c62ad8b
added Blueprints for mscolab
JanEisermann Mar 17, 2026
b11e00b
fixxed flake8 errors
JanEisermann Mar 17, 2026
ce2ef58
fixed incompleted address errors
JanEisermann Mar 20, 2026
18ff5ed
added Jan Eisermann to the authors list
JanEisermann Mar 23, 2026
2f8f139
moved methods to the rigth blueprint path and added description to th…
JanEisermann Mar 23, 2026
570f809
moved Jan Eisermann to the right place in the author list
JanEisermann Mar 23, 2026
80c25b7
moved each blueprint in mscolab and mswms into its own directory (inc…
JanEisermann Mar 24, 2026
aea0401
corrected the directory path in the doc string
JanEisermann Mar 25, 2026
efb2dc6
renamed from status.html to status_password.html
JanEisermann Mar 25, 2026
47e7c3e
added doc string
JanEisermann Mar 25, 2026
b9393d8
corrected doc string
JanEisermann Mar 25, 2026
db4a8bc
moved into blueprins.docs.templates (mscolab and mswms)
JanEisermann Mar 25, 2026
f4ef61f
moved the routes into the respective blueprints.
JanEisermann Mar 25, 2026
0a6b15e
added content.html to mslib.mswms.blueprints.gallery.templates.gallery
JanEisermann Mar 25, 2026
d6f1716
moved APP.routes('/') from mslib/mscolab/server.py to docs Blueprint
JanEisermann Mar 25, 2026
ed5d2de
moved APP.routes('/') from mslib/mswms/wms to docs Blueprint
JanEisermann Mar 25, 2026
4fa09d5
moved static directory to blueprints
JanEisermann Mar 26, 2026
d7227ea
moved static directory to blueprints
JanEisermann Mar 26, 2026
cb54df2
fixed the Mission Support System
JanEisermann Mar 27, 2026
c843548
fixed the Mission Support System Gallary
JanEisermann Mar 27, 2026
a1de8d0
fixed URL for reset_request
JanEisermann Mar 27, 2026
067fb8d
changed imprint = None and gdpr = None
JanEisermann Mar 27, 2026
ae5f232
fixed URLs
JanEisermann Apr 7, 2026
5ae62ea
fixed URL
JanEisermann Apr 7, 2026
82e4476
added blank line
JanEisermann Apr 7, 2026
2955043
changed that current_app is used instead of APP
JanEisermann Apr 7, 2026
09abb66
fixed flake8 errors
JanEisermann Apr 7, 2026
fde16dc
changed that current_app is used instead of APP
JanEisermann Apr 7, 2026
ee1d8b4
Merge branch 'develop' into inkludingBP
JanEisermann Apr 7, 2026
a84d194
origin default settings
JanEisermann Apr 20, 2026
3093e2a
fixed SAML2 login
JanEisermann Apr 20, 2026
80a67fc
catch EmailUndeliverableError
JanEisermann Apr 20, 2026
3ea56e1
moved the mscolab-coupled helpers to mscolab.auth
JanEisermann Apr 20, 2026
7b6a5d7
added description header
JanEisermann Apr 20, 2026
8c60015
tried to remove workarounds for circular Imports (does not work at th…
JanEisermann Apr 21, 2026
8547d43
removed workarounds for circular Imports
JanEisermann Apr 24, 2026
476d815
renamed getMail to get_mail
JanEisermann Apr 24, 2026
d94a344
moved Blueprint registrations into create_app
JanEisermann Apr 24, 2026
895c3d2
corrected status code
JanEisermann Apr 24, 2026
a396cbc
fixed reset_request URL in status_password template context
JanEisermann Apr 24, 2026
cda0bae
added default values for imprint and gdpr
JanEisermann Apr 24, 2026
14f074e
Typo and removed dead parameter
JanEisermann Apr 24, 2026
b29626d
fixed attribute error by ensuring jump has .size before access
JanEisermann Apr 24, 2026
36b1cdc
fixed gallery default tab logic to prioritize "Top" plot type when av…
JanEisermann Apr 24, 2026
76b1e42
register_user is now imported from one location
JanEisermann Apr 24, 2026
f6775ea
flake8
JanEisermann Apr 24, 2026
174ea41
removed validate_email since the import is no longer used
JanEisermann Apr 24, 2026
f9f4a52
explicit url path for each blueprint
JanEisermann Apr 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ in alphabetic order by first name
- Debajyoti Dasgupta <debajyotidasgupta6@gmail.com>
- Hrithik Kumar Verma <vermahrithik812@gmail.com>
- Isabell Krisch <isabellkrisch@gmail.com>
- Jan Eisermann <j.eisermann@fz-juelich.de>
- Jatin Jain <jatinalwar2001@gmail.com>
- Jens-Uwe Grooß <j.-u.grooss@fz-juelich.de>
- Jörn Ungermann <j.ungermann@fz-juelich.de>
Expand Down
135 changes: 32 additions & 103 deletions mslib/mscolab/app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,45 +33,41 @@

import mslib

from flask import render_template, send_from_directory, send_file, url_for, abort
from flask import url_for
from flask_sqlalchemy import SQLAlchemy

from mslib.mscolab.blueprints.docs import DOCS_BP
from mslib.mscolab.conf import mscolab_settings
from mslib.utils import prefix_route, release_info
from mslib.msui.icons import icons
from mslib.utils.get_content import get_content
from mslib.utils.file_exists import file_exists
from xstatic.main import XStatic

message, update = release_info.check_for_new_release()
if update:
logging.warning(message)


def file_exists(filepath=None):
try:
return os.path.isfile(filepath)
except TypeError:
return False


DOCS_SERVER_PATH = os.path.dirname(os.path.abspath(mslib.__file__))
DOCS_STATIC_DIR = os.path.join(DOCS_SERVER_PATH, 'static')
DOCS_BLUEPRINTS_DIR = os.path.join(DOCS_SERVER_PATH, 'blueprints')
DOCS_STATIC_DIR = os.path.join(DOCS_BLUEPRINTS_DIR, 'static')
DOCS_IMG_DIR = os.path.join(DOCS_STATIC_DIR, 'img')
DOCS_DOCS_DIR = os.path.join(DOCS_STATIC_DIR, 'docs')
DOCS_TEMPLATES_DIR = os.path.join(DOCS_STATIC_DIR, 'templates')
# This can be used to set a location by SCRIPT_NAME for testing. e.g. export SCRIPT_NAME=/demo/
SCRIPT_NAME = os.environ.get('SCRIPT_NAME', '/')


message, update = release_info.check_for_new_release()
if update:
logging.warning(message)


# in memory database for testing
# app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///'
APP = Flask(__name__, template_folder=os.path.join(DOCS_STATIC_DIR, 'templates'))
APP = Flask(__name__, template_folder=os.path.join(DOCS_TEMPLATES_DIR))
APP.jinja_env.globals.setdefault("imprint", "")
APP.jinja_env.globals.setdefault("gdpr", "")
APP.config.from_object(mscolab_settings)
# Expose docs path for callers/tests and make it part of Flask config for consistency.
APP.config['DOCS_SERVER_PATH'] = DOCS_SERVER_PATH
APP.route = prefix_route(APP.route, SCRIPT_NAME)

APP.jinja_env.globals.update(file_exists=file_exists)
APP.jinja_env.globals["imprint"] = APP.config['IMPRINT']
APP.jinja_env.globals["gdpr"] = APP.config['GDPR']


def _xstatic(name):
mod_names = [
Expand Down Expand Up @@ -100,87 +96,20 @@ def create_app(imprint=None, gdpr=None):
gdpr_file = gdpr

APP.jinja_env.globals.update(file_exists=file_exists)
APP.jinja_env.globals["imprint"] = imprint_file
APP.jinja_env.globals["gdpr"] = gdpr_file

@APP.route('/xstatic/<name>/<path:filename>')
def files(name, filename):
base_path = _xstatic(name)
if base_path is None:
abort(404)
if not filename:
abort(404)
return send_from_directory(base_path, filename)

@APP.route('/mss_theme/img/<path:filename>')
def mss_theme(filename):
base_path = os.path.join(DOCS_IMG_DIR)
return send_from_directory(base_path, filename)

APP.jinja_env.globals["imprint"] = imprint_file or ""
APP.jinja_env.globals["gdpr"] = gdpr_file or ""
APP.jinja_env.globals.update(get_topmenu=get_topmenu)

@APP.route("/index")
def index():
return render_template("/index.html")

@APP.route("/mss/about")
@APP.route("/mss")
def about():
_file = os.path.join(DOCS_DOCS_DIR, 'about.md')
img_url = url_for('overview')
md_overrides = ('![image](/mss/overview.png)', f'![image]({img_url})')

html_overrides = ('<img alt="image" src="/mss/overview.png" />',
'<img class="mx-auto d-block img-fluid" alt="image" src="/mss/overview.png" />')
content = get_content(_file, md_overrides=md_overrides, html_overrides=html_overrides)
return render_template("/content.html", act="about", content=content)

@APP.route("/mss/install")
def install():
_file = os.path.join(DOCS_DOCS_DIR, 'installation.md')
content = get_content(_file)
return render_template("/content.html", act="install", content=content)

@APP.route("/mss/help")
def help(): # noqa: A001
_file = os.path.join(DOCS_DOCS_DIR, 'help.md')
html_overrides = ('<img alt="Waypoint Tutorial" '
'src="https://mss.readthedocs.io/en/stable/_images/tutorial_waypoints.gif" />',
'<img class="mx-auto d-block img-fluid" alt="Waypoint Tutorial" '
'src="https://mss.readthedocs.io/en/stable/_images/tutorial_waypoints.gif" />')
content = get_content(_file, html_overrides=html_overrides)
return render_template("/content.html", act="help", content=content)

@APP.route("/mss/imprint")
def imprint():
if file_exists(imprint_file):
content = get_content(imprint_file)
return render_template("/content.html", act="imprint", content=content)
else:
return ""

@APP.route("/mss/gdpr")
def gdpr():
if file_exists(gdpr_file):
content = get_content(gdpr_file)
return render_template("/content.html", act="gdpr", content=content)
else:
return ""

@APP.route('/mss/favicon.ico')
def favicons():
base_path = icons("16x16", "favicon.ico")
return send_file(base_path)

@APP.route('/mss/logo.png')
def logo():
base_path = icons("64x64", "mss-logo.png")
return send_file(base_path)

@APP.route('/mss/overview.png')
def overview():
base_path = os.path.join(DOCS_IMG_DIR, 'wise12_overview.png')
return send_file(base_path)
from mslib.mscolab.blueprints.auth import AUTH_BP
from mslib.mscolab.blueprints.chat import CHAT_BP
from mslib.mscolab.blueprints.operation import OPERATION_BP
from mslib.mscolab.blueprints.user import USER_BP

APP.register_blueprint(AUTH_BP)
APP.register_blueprint(CHAT_BP)
APP.register_blueprint(USER_BP)
APP.register_blueprint(OPERATION_BP)
APP.register_blueprint(DOCS_BP)

return APP

Expand All @@ -205,10 +134,10 @@ def overview():

def get_topmenu():
menu = [
(url_for('index'), 'Mission Support System',
((url_for('about'), 'About'),
(url_for('install'), 'Install'),
(url_for('help'), 'Help'),
(url_for('docs.index'), 'Mission Support System',
((url_for('docs.about'), 'About'),
(url_for('docs.install'), 'Install'),
(url_for('docs.help'), 'Help'),
)),
]
return menu
165 changes: 165 additions & 0 deletions mslib/mscolab/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
# -*- coding: utf-8 -*-
"""

mslib.utils.auth
~~~~~~~~~~~~~~~~

handles passwords from the keyring for login and http_auuth


To better understand of the code, look at the 'ships' example from
chapter 14/16 of 'Rapid GUI Programming with Python and Qt: The
Definitive Guide to PyQt Programming' (Mark Summerfield).

This file is part of MSS.

:copyright: Copyright 2023 Reimar Bauer
:copyright: Copyright 2023-2026 by the MSS team, see AUTHORS.
:license: APACHE-2.0, see LICENSE for details.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""


import datetime
import functools
import logging

import email_validator
import sqlalchemy
from flask import current_app, request, abort, g
from itsdangerous import URLSafeTimedSerializer, BadSignature

from mslib.mscolab.conf import setup_saml2_backend
from mslib.mscolab.models import User


def check_login(emailid, password):
try:
user = User.query.filter_by(emailid=str(emailid)).first()
except sqlalchemy.exc.OperationalError as ex:
logging.debug("Problem in the database (%ex), likely version client different", ex)
return False
if user is not None:
if current_app.config['MAIL_ENABLED']:
if user.confirmed:
if user.verify_password(password):
return user
else:
if user.verify_password(password):
return user
return False


def register_user(email, password, username, fullname):
if len(str(email.strip())) == 0 or len(str(username.strip())) == 0:
return {"success": False, "message": "Your username or email cannot be empty"}
is_valid_username = True if username.find("@") == -1 else False
try:
# ToDo verify what changed for check_deliverability
email_validator.validate_email(email, check_deliverability=current_app.config['MAIL_ENABLED'])
except (email_validator.exceptions.EmailSyntaxError or email_validator.exceptions.EmailUndeliverableError):
return {"success": False, "message": "Your email ID is not valid!"}
if not is_valid_username:
return {"success": False, "message": "Your username cannot contain @ symbol!"}
user_exists = User.query.filter_by(emailid=str(email)).first()
if user_exists:
return {"success": False, "message": "This email ID is already taken!"}
user_exists = User.query.filter_by(username=str(username)).first()
if user_exists:
return {"success": False, "message": "This username is already registered"}
fm = current_app.extensions['fm']
user = User(email, username, password, fullname)
result = fm.modify_user(user, action="create")
return {"success": result}


def verify_user(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
try:
user = User.verify_auth_token(request.args.get('token', request.form.get('token', False)))
except TypeError:
logging.debug("no token in request form")
abort(404)
if not user:
return "False"
else:
# saving user details in flask.g
if current_app.config['MAIL_ENABLED']:
if user.confirmed:
g.user = user
return func(*args, **kwargs)
else:
return "False"
else:
g.user = user
return func(*args, **kwargs)
return wrapper


def confirm_token(token, expiration=3600):
serializer = URLSafeTimedSerializer(current_app.config['SECRET_KEY'])
try:
email = serializer.loads(
token,
salt=current_app.config['SECURITY_PASSWORD_SALT'],
max_age=expiration
)
except (IOError, BadSignature):
return False
return email


def generate_confirmation_token(email):
serializer = URLSafeTimedSerializer(current_app.config['SECRET_KEY'])
return serializer.dumps(email, salt=current_app.config['SECURITY_PASSWORD_SALT'])


def get_idp_entity_id(selected_idp):
"""
Finds the entity_id from the configured IDPs
:return: the entity_id of the idp or None
"""
for config in setup_saml2_backend.CONFIGURED_IDPS:
if selected_idp == config['idp_identity_name']:
idps = config['idp_data']['saml2client'].metadata.identity_providers()
only_idp = idps[0]
entity_id = only_idp
return entity_id
return None


def create_or_update_idp_user(email, username, token, authentication_backend):
"""
Creates or updates an idp user in the system based on the provided email,
username, token, and authentication backend.
:param email: idp users email
:param username: idp users username
:param token: authentication token
:param authentication_backend: authenticated identity providers name
:return: bool : query success or not
"""
fm = current_app.extensions['fm']
user = User.query.filter_by(emailid=email).first()
Comment thread
JanEisermann marked this conversation as resolved.
if not user:
# using an IDP for a new account/profile, e-mail is already verified by the IDP
confirm_time = datetime.datetime.now(tz=datetime.timezone.utc) + datetime.timedelta(seconds=1)
user = User(email, username, password=token, confirmed=True, confirmed_on=confirm_time,
authentication_backend=authentication_backend)
result = fm.modify_user(user, action="create")
else:
user.authentication_backend = authentication_backend
user.hash_password(token)
result = fm.modify_user(user, action="update_idp_user")
return result
Loading
Loading