Skip to content

Commit

Permalink
refactored for Pallet projects (#1668)
Browse files Browse the repository at this point in the history
* replaced TimedJSONWebSignatureSerializer by jwt

* test for tokens added

* refactored for flask-sqlalchemy >=3.0.0

* app module for mswms

* flake8

* packagename fixed
  • Loading branch information
ReimarBauer authored Feb 28, 2023
1 parent 8e80d4f commit 1146086
Show file tree
Hide file tree
Showing 22 changed files with 217 additions and 106 deletions.
8 changes: 4 additions & 4 deletions localbuild/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,15 @@ requirements:
- fs_filepicker
- cftime >=1.0.1
- matplotlib >=3.3.2,<3.6
- itsdangerous <2.1.0
- flask >=2.0.0,<2.2.0
- itsdangerous
- pyjwt
- flask >=2.2.0
- flask-httpauth
- flask-mail
- flask-migrate
- sqlalchemy <2.0.0
- werkzeug >2.0.0
- flask-socketio =5.1.0
- flask-sqlalchemy
- flask-sqlalchemy >=3.0.0
- flask-cors
- passlib
- gitpython
Expand Down
24 changes: 5 additions & 19 deletions mslib/index.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
import mslib

from flask import render_template
from flask import Flask
from flask import send_from_directory, send_file, url_for
from flask import abort
from flask import request
Expand Down Expand Up @@ -65,24 +64,11 @@ def _xstatic(name):
return None


def prefix_route(route_function, prefix='', mask='{0}{1}'):
'''
https://stackoverflow.com/questions/18967441/add-a-prefix-to-all-flask-routes/18969161#18969161
Defines a new route function with a prefix.
The mask argument is a `format string` formatted with, in that order:
prefix, route
'''
def newroute(route, *args, **kwargs):
''' prefix route '''
return route_function(mask.format(prefix, route), *args, **kwargs)
return newroute


def app_loader(name):
APP = Flask(name, template_folder=os.path.join(DOCS_SERVER_PATH, 'static', 'templates'), static_url_path="/static",
static_folder=STATIC_LOCATION)
APP.config.from_object(name)
APP.route = prefix_route(APP.route, SCRIPT_NAME)
def create_app(name=""):
if "mscolab.server" in name:
from mslib.mscolab.app import APP
else:
from mslib.mswms.app import APP

@APP.route('/xstatic/<name>/', defaults=dict(filename=''))
@APP.route('/xstatic/<name>/<path:filename>')
Expand Down
35 changes: 30 additions & 5 deletions mslib/mscolab/app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,37 @@
limitations under the License.
"""

import os
import mslib

from flask_migrate import Migrate
from mslib.mscolab.models import db
from mslib.mscolab.server import _app as app
from flask import Flask
from mslib.mscolab.conf import mscolab_settings
from mslib.mswms.gallery_builder import STATIC_LOCATION
from mslib.utils import prefix_route


DOCS_SERVER_PATH = os.path.dirname(os.path.abspath(mslib.__file__))
# 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', '/')

# in memory database for testing
# app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///'

migrate = Migrate(app, db, render_as_batch=True)
APP = Flask(__name__, template_folder=os.path.join(DOCS_SERVER_PATH, 'static', 'templates'), static_url_path="/static",
static_folder=STATIC_LOCATION)
APP.config.from_object(__name__)
APP.route = prefix_route(APP.route, SCRIPT_NAME)

APP.config['MSCOLAB_DATA_DIR'] = mscolab_settings.MSCOLAB_DATA_DIR
APP.config['SQLALCHEMY_DATABASE_URI'] = mscolab_settings.SQLALCHEMY_DB_URI
APP.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
APP.config['UPLOAD_FOLDER'] = mscolab_settings.UPLOAD_FOLDER
APP.config['MAX_CONTENT_LENGTH'] = mscolab_settings.MAX_UPLOAD_SIZE
APP.config['SECRET_KEY'] = mscolab_settings.SECRET_KEY
APP.config['SECURITY_PASSWORD_SALT'] = getattr(mscolab_settings, "SECURITY_PASSWORD_SALT", None)
APP.config['MAIL_DEFAULT_SENDER'] = getattr(mscolab_settings, "MAIL_DEFAULT_SENDER", None)
APP.config['MAIL_SERVER'] = getattr(mscolab_settings, "MAIL_SERVER", None)
APP.config['MAIL_PORT'] = getattr(mscolab_settings, "MAIL_PORT", None)
APP.config['MAIL_USERNAME'] = getattr(mscolab_settings, "MAIL_USERNAME", None)
APP.config['MAIL_PASSWORD'] = getattr(mscolab_settings, "MAIL_PASSWORD", None)
APP.config['MAIL_USE_TLS'] = getattr(mscolab_settings, "MAIL_USE_TLS", None)
APP.config['MAIL_USE_SSL'] = getattr(mscolab_settings, "MAIL_USE_SSL", None)
40 changes: 24 additions & 16 deletions mslib/mscolab/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@
import datetime
import enum
import logging
import jwt

from flask_sqlalchemy import SQLAlchemy
from itsdangerous import (BadSignature, SignatureExpired, TimedJSONWebSignatureSerializer as Serializer)
from passlib.apps import custom_app_context as pwd_context
from flask_sqlalchemy import SQLAlchemy
from mslib.mscolab.app import APP


db = SQLAlchemy()
db = SQLAlchemy(APP)


class User(db.Model):
Expand Down Expand Up @@ -67,14 +67,20 @@ def verify_password(self, password_):
return pwd_context.verify(password_, self.password)

def generate_auth_token(self, expiration=None):
# ToDo cleanup API
# Importing conf here to avoid loading settings on opening chat window
from mslib.mscolab.conf import mscolab_settings
expiration = mscolab_settings.__dict__.get('EXPIRATION', expiration)
if expiration is None:
expiration = 864000
s = Serializer(mscolab_settings.SECRET_KEY, expires_in=expiration)
return s.dumps({'id': self.id})
token = jwt.encode(
{
"id": self.id,
"exp": datetime.datetime.now(tz=datetime.timezone.utc) + datetime.timedelta(seconds=expiration)
},
mscolab_settings.SECRET_KEY,
algorithm="HS256"
)
return token

@staticmethod
def verify_auth_token(token):
Expand All @@ -83,16 +89,18 @@ def verify_auth_token(token):
"""
# Importing conf here to avoid loading settings on opening chat window
from mslib.mscolab.conf import mscolab_settings
s = Serializer(mscolab_settings.SECRET_KEY)
try:
data = s.loads(token)
except SignatureExpired:
logging.debug("Signature Expired")
return None # valid token, but expired
except BadSignature:
logging.debug("Bad Signature")
return None # invalid token
user = User.query.filter_by(id=data['id']).first()
data = jwt.decode(
token,
mscolab_settings.SECRET_KEY,
leeway=datetime.timedelta(seconds=30),
algorithms=["HS256"]
)
except Exception as e:
logging.debug("Bad Token %s", str(e))
return None

user = User.query.filter_by(id=data.get('id')).first()
return user


Expand Down
6 changes: 4 additions & 2 deletions mslib/mscolab/mscolab.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,17 @@ def confirm_action(confirmation_prompt):


def handle_db_init():
from mslib.mscolab.server import APP, db
from mslib.mscolab.models import db
from mslib.mscolab.server import APP
create_files()
with APP.app_context():
db.create_all()
print("Database initialised successfully!")


def handle_db_reset(verbose=True):
from mslib.mscolab.server import APP, db
from mslib.mscolab.models import db
from mslib.mscolab.server import APP
if os.path.exists(mscolab_settings.DATA_DIR):
shutil.rmtree(mscolab_settings.DATA_DIR)
create_files()
Expand Down
16 changes: 1 addition & 15 deletions mslib/mscolab/seed.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,18 @@
"""
import logging
import fs
from flask import Flask
import git
from sqlalchemy.exc import IntegrityError

from mslib.mscolab.conf import mscolab_settings
from mslib.mscolab.models import User, db, Permission, Operation


app = Flask(__name__, static_url_path='')
from mslib.mscolab.server import APP as app


def add_all_users_to_all_operations(access_level='collaborator'):
""" on db level we add all users as collaborator to all operations """
app.config['SQLALCHEMY_DATABASE_URI'] = mscolab_settings.SQLALCHEMY_DB_URI
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db.init_app(app)
with app.app_context():
all_operations = Operation.query.all()
all_path = [operation.path for operation in all_operations]
Expand All @@ -58,7 +54,6 @@ def add_all_users_default_operation(path='TEMPLATE', description="Operation to k
app.config['SQLALCHEMY_DATABASE_URI'] = mscolab_settings.SQLALCHEMY_DB_URI
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db.init_app(app)
with app.app_context():
operation_available = Operation.query.filter_by(path=path).first()
if not operation_available:
Expand Down Expand Up @@ -100,7 +95,6 @@ def add_all_users_default_operation(path='TEMPLATE', description="Operation to k
def delete_user(email):
app.config['SQLALCHEMY_DATABASE_URI'] = mscolab_settings.SQLALCHEMY_DB_URI
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db.init_app(app)
with app.app_context():
user = User.query.filter_by(emailid=str(email)).first()
if user:
Expand All @@ -119,7 +113,6 @@ def add_user(email, username, password):
"""
app.config['SQLALCHEMY_DATABASE_URI'] = mscolab_settings.SQLALCHEMY_DB_URI
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db.init_app(app)

template = f"""
"MSCOLAB_mailid": "{email}",
Expand Down Expand Up @@ -154,7 +147,6 @@ def get_operation(operation_name):
def add_operation(operation_name, description):
app.config['SQLALCHEMY_DATABASE_URI'] = mscolab_settings.SQLALCHEMY_DB_URI
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db.init_app(app)
with app.app_context():
operation_available = Operation.query.filter_by(path=operation_name).first()
if not operation_available:
Expand All @@ -178,7 +170,6 @@ def add_operation(operation_name, description):
def delete_operation(operation_name):
app.config['SQLALCHEMY_DATABASE_URI'] = mscolab_settings.SQLALCHEMY_DB_URI
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db.init_app(app)
with app.app_context():
operation = Operation.query.filter_by(path=operation_name).first()
if operation:
Expand All @@ -196,12 +187,9 @@ def add_user_to_operation(path=None, access_level='admin', emailid=None):
return False
app.config['SQLALCHEMY_DATABASE_URI'] = mscolab_settings.SQLALCHEMY_DB_URI
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db.init_app(app)
with app.app_context():
operation = Operation.query.filter_by(path=path).first()
if operation:

user = User.query.filter_by(emailid=emailid).first()
if user:
new_permissions = [Permission(user.id, operation.id, access_level)]
Expand All @@ -219,8 +207,6 @@ def add_user_to_operation(path=None, access_level='admin', emailid=None):
def seed_data():
app.config['SQLALCHEMY_DATABASE_URI'] = mscolab_settings.SQLALCHEMY_DB_URI
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db.init_app(app)

with app.app_context():
# create users
users = [{
Expand Down
32 changes: 8 additions & 24 deletions mslib/mscolab/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
from flask import send_from_directory, abort, url_for
from flask_mail import Mail, Message
from flask_cors import CORS
from flask_migrate import Migrate
from flask_httpauth import HTTPBasicAuth
from validate_email import validate_email
from werkzeug.utils import secure_filename
Expand All @@ -48,30 +49,13 @@
from mslib.mscolab.sockets_manager import setup_managers
from mslib.mscolab.utils import create_files, get_message_dict
from mslib.utils import conditional_decorator
from mslib.index import app_loader
from mslib.index import create_app

APP = app_loader(__name__)

APP = create_app(__name__)
mail = Mail(APP)
CORS(APP, origins=mscolab_settings.CORS_ORIGINS if hasattr(mscolab_settings, "CORS_ORIGINS") else ["*"])

# set the operation root directory as the static folder
# ToDo needs refactoring on a route without using of static folder

APP.config['MSCOLAB_DATA_DIR'] = mscolab_settings.MSCOLAB_DATA_DIR
APP.config['SQLALCHEMY_DATABASE_URI'] = mscolab_settings.SQLALCHEMY_DB_URI
APP.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
APP.config['UPLOAD_FOLDER'] = mscolab_settings.UPLOAD_FOLDER
APP.config['MAX_CONTENT_LENGTH'] = mscolab_settings.MAX_UPLOAD_SIZE
APP.config['SECRET_KEY'] = mscolab_settings.SECRET_KEY
APP.config['SECURITY_PASSWORD_SALT'] = getattr(mscolab_settings, "SECURITY_PASSWORD_SALT", None)
APP.config['MAIL_DEFAULT_SENDER'] = getattr(mscolab_settings, "MAIL_DEFAULT_SENDER", None)
APP.config['MAIL_SERVER'] = getattr(mscolab_settings, "MAIL_SERVER", None)
APP.config['MAIL_PORT'] = getattr(mscolab_settings, "MAIL_PORT", None)
APP.config['MAIL_USERNAME'] = getattr(mscolab_settings, "MAIL_USERNAME", None)
APP.config['MAIL_PASSWORD'] = getattr(mscolab_settings, "MAIL_PASSWORD", None)
APP.config['MAIL_USE_TLS'] = getattr(mscolab_settings, "MAIL_USE_TLS", None)
APP.config['MAIL_USE_SSL'] = getattr(mscolab_settings, "MAIL_USE_SSL", None)

migrate = Migrate(APP, db, render_as_batch=True)
auth = HTTPBasicAuth()

try:
Expand Down Expand Up @@ -144,7 +128,7 @@ def initialize_managers(app):
# initializing socketio and db
app.wsgi_app = socketio.Middleware(socketio.server, app.wsgi_app)
sockio.init_app(app)
db.init_app(app)
# db.init_app(app)
return app, sockio, cm, fm


Expand Down Expand Up @@ -234,14 +218,14 @@ def get_auth_token():
if user.confirmed:
token = user.generate_auth_token()
return json.dumps({
'token': token.decode('ascii'),
'token': token,
'user': {'username': user.username, 'id': user.id}})
else:
return "False"
else:
token = user.generate_auth_token()
return json.dumps({
'token': token.decode('ascii'),
'token': token,
'user': {'username': user.username, 'id': user.id}})
else:
logging.debug("Unauthorized user: %s", emailid)
Expand Down
44 changes: 44 additions & 0 deletions mslib/mswms/app/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
"""
mslib.mswms.app
~~~~~~~~~~~~~~~
app module of mswms
This file is part of MSS.
:copyright: Copyright 2016-2022 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 os
import mslib

from flask import Flask
from mslib.mswms.gallery_builder import STATIC_LOCATION
from mslib.utils import prefix_route


DOCS_SERVER_PATH = os.path.dirname(os.path.abspath(mslib.__file__))
# 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', '/')

# in memory database for testing
# app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///'
APP = Flask(__name__, template_folder=os.path.join(DOCS_SERVER_PATH, 'static', 'templates'), static_url_path="/static",
static_folder=STATIC_LOCATION)
APP.config.from_object(__name__)
APP.route = prefix_route(APP.route, SCRIPT_NAME)
Loading

0 comments on commit 1146086

Please sign in to comment.