csrf, openid_connect

This commit is contained in:
Daniel Tsvetkov 2021-05-09 00:05:26 +02:00
parent bad151eb43
commit 0e869bd55a
9 changed files with 111 additions and 10 deletions

View File

@ -23,3 +23,5 @@ STATIC_FOLDER = os.path.join(basepath, "webapp", "static")
MAKEDIRS = [ MAKEDIRS = [
DATA_DIR, STATIC_DATA_DIR, MEDIA_DIR, TASKS_DIR, TASKS_IN_DIR, TASKS_PROC_DIR, TASKS_BUF_DIR, DATA_DIR, STATIC_DATA_DIR, MEDIA_DIR, TASKS_DIR, TASKS_IN_DIR, TASKS_PROC_DIR, TASKS_BUF_DIR,
] ]
APP_BASE_URL = "http://localhost:5000"

View File

@ -1,3 +1,4 @@
import base64
import csv import csv
import datetime import datetime
import json import json
@ -8,31 +9,35 @@ from importlib import import_module
from json import JSONEncoder from json import JSONEncoder
from uuid import uuid4 from uuid import uuid4
import onetimepass
from flask import request from flask import request
from flask_security import current_user
from config import SQLALCHEMY_DATABASE_URI, MAKEDIRS, DATABASE_FILE, SEARCH_INDEX_PATH, STATIC_DATA_DIR, MEDIA_DIR, basepath
from flask_migrate import Migrate from flask_migrate import Migrate
from flask_migrate import upgrade as migrate_upgrade
from flask_migrate import init as migrate_init from flask_migrate import init as migrate_init
from flask_migrate import upgrade as migrate_upgrade
from flask_security import RoleMixin, UserMixin from flask_security import RoleMixin, UserMixin
from flask_security import Security, SQLAlchemyUserDatastore from flask_security import Security, SQLAlchemyUserDatastore
from flask_security.utils import encrypt_password, hash_password from flask_security import current_user
from flask_security.utils import hash_password
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
from flask_wtf import CSRFProtect
from markupsafe import escape, Markup from markupsafe import escape, Markup
from sqlalchemy import Boolean from sqlalchemy import Boolean
from sqlalchemy import TypeDecorator from sqlalchemy import TypeDecorator
from sqlalchemy.ext.declarative import declared_attr, DeclarativeMeta from sqlalchemy.ext.declarative import declared_attr, DeclarativeMeta
from sqlalchemy.orm.collections import InstrumentedList from sqlalchemy.orm.collections import InstrumentedList
from sqlalchemy_utils import Choice from sqlalchemy_utils import Choice
from tww.lib import solve_query, resolve_timezone, dt_tz_translation, time_ago, get_utcnow from tww.lib import solve_query, resolve_timezone, dt_tz_translation, time_ago
from werkzeug.security import generate_password_hash, check_password_hash
from whooshalchemy import IndexService from whooshalchemy import IndexService
from config import SQLALCHEMY_DATABASE_URI, MAKEDIRS, DATABASE_FILE, SEARCH_INDEX_PATH, STATIC_DATA_DIR, MEDIA_DIR, \
basepath
from oshipka.util.strings import camel_case_to_snake_case from oshipka.util.strings import camel_case_to_snake_case
from vm_gen.vm_gen import order_from_process_order from vm_gen.vm_gen import order_from_process_order
db = SQLAlchemy() db = SQLAlchemy()
migrate = Migrate() migrate = Migrate()
csrf = CSRFProtect()
SHARING_TYPE_TYPES_TYPE_PUBLIC = "PUBLIC" SHARING_TYPE_TYPES_TYPE_PUBLIC = "PUBLIC"
SHARING_TYPE_TYPES_TYPE_AUTHZ = "AUTHZ" SHARING_TYPE_TYPES_TYPE_AUTHZ = "AUTHZ"
@ -64,7 +69,6 @@ roles_users = db.Table('roles_users',
db.Column('user_id', db.Integer(), db.ForeignKey('user.id')), db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
db.Column('role_id', db.Integer(), db.ForeignKey('role.id'))) db.Column('role_id', db.Integer(), db.ForeignKey('role.id')))
class ModelJsonEncoder(JSONEncoder): class ModelJsonEncoder(JSONEncoder):
def default(self, o): def default(self, o):
if isinstance(o, datetime.datetime): if isinstance(o, datetime.datetime):
@ -190,6 +194,49 @@ class User(db.Model, ModelController, UserMixin):
backref=db.backref('users', lazy='dynamic')) backref=db.backref('users', lazy='dynamic'))
class U2FCredential(db.Model, ModelController):
name = db.Column(db.Unicode)
date_added = db.Column(db.DateTime)
device = db.Column(db.Unicode)
user_sso_id = db.Column(db.Integer, db.ForeignKey('user_sso.id'))
user_sso = db.relationship('UserSSO',
backref=db.backref("u2f_credentials"),
)
class UserSSO(db.Model, ModelController):
username = db.Column(db.Unicode, unique=True)
email = db.Column(db.Unicode, unique=True)
password_hash = db.Column(db.Unicode)
otp_secret = db.Column(db.String(16))
def __init__(self, **kwargs):
super(UserSSO, self).__init__(**kwargs)
if self.otp_secret is None:
# generate a random secret
self.otp_secret = base64.b32encode(os.urandom(10)).decode('utf-8')
@property
def password(self):
raise AttributeError('password is not a readable attribute')
@password.setter
def password(self, password):
self.password_hash = generate_password_hash(password)
def verify_password(self, password):
return check_password_hash(self.password_hash, password)
def get_totp_uri(self, client_name):
return 'otpauth://totp/{client_name}:{username}?secret={secret}&issuer={client_name}' \
.format(username=self.username, secret=self.otp_secret, client_name=client_name)
def verify_totp(self, token):
return onetimepass.valid_totp(token, self.otp_secret)
security = Security() security = Security()
user_datastore = SQLAlchemyUserDatastore(db, User, Role) user_datastore = SQLAlchemyUserDatastore(db, User, Role)
@ -335,6 +382,7 @@ def init_db(app):
app.register_blueprint(oshipka_bp) app.register_blueprint(oshipka_bp)
db.init_app(app) db.init_app(app)
csrf.init_app(app)
migrate.init_app(app, db) migrate.init_app(app, db)
security.init_app(app, user_datastore) security.init_app(app, user_datastore)
_init_translations(app) _init_translations(app)

View File

@ -1,9 +1,32 @@
from flask import send_from_directory import urllib
import requests
from flask import send_from_directory, redirect, request, url_for
from oshipka.webapp import oshipka_bp from oshipka.webapp import oshipka_bp
from config import MEDIA_DIR from config import MEDIA_DIR, APP_BASE_URL
# TODO: VULNZ - EVERYONE HAS ACCESS TO THIS
@oshipka_bp.route('/media/<path:filepath>') @oshipka_bp.route('/media/<path:filepath>')
def get_media(filepath): def get_media(filepath):
return send_from_directory(MEDIA_DIR, filepath) return send_from_directory(MEDIA_DIR, filepath)
SSO_BASE_URL = 'http://localhost:5008'
@oshipka_bp.route('/sso')
def sso():
callback_url = APP_BASE_URL + url_for('oshipka_bp.open_id_connect_code')
return redirect(SSO_BASE_URL + '/authenticate?callback={}'.format(urllib.parse.quote(callback_url)))
@oshipka_bp.route('/open_id_connect_code')
def open_id_connect_code():
code = request.args.get('code')
response = requests.get(
SSO_BASE_URL + "/token",
data={'code': code},
)
return 'got response for token: {}'.format(response.status_code)

View File

@ -3,6 +3,7 @@
{% block content %} {% block content %}
<h1>Delete {{ model_view.model_name }}:{{ instance.id }} ?</h1> <h1>Delete {{ model_view.model_name }}:{{ instance.id }} ?</h1>
<form action="{{ url_for('delete_' + model_view.model_name, uuid=instance.uuid) }}" method="post"> <form action="{{ url_for('delete_' + model_view.model_name, uuid=instance.uuid) }}" method="post">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<input type="hidden" value="{{ request.args._next or url_for('list_' + model_view.model_name) }}" name="_next"/> <input type="hidden" value="{{ request.args._next or url_for('list_' + model_view.model_name) }}" name="_next"/>
<input type="submit"/> <input type="submit"/>
</form> </form>

View File

@ -24,9 +24,12 @@ inflect==4.1.0
itsdangerous==1.1.0 itsdangerous==1.1.0
Jinja2==2.11.1 Jinja2==2.11.1
MarkupSafe==1.1.1 MarkupSafe==1.1.1
onetimepass==1.0.1
passlib==1.7.2 passlib==1.7.2
pathtools==0.1.2 pathtools==0.1.2
pycparser==2.20 pycparser==2.20
pyqrcode==1.2.1
python-u2flib-server==5.0.1
pytz==2019.3 pytz==2019.3
pyyaml==5.3.1 pyyaml==5.3.1
six==1.14.0 six==1.14.0

View File

@ -1,4 +1,5 @@
<form action="{{ url_for('create_[[ name|camel_to_snake ]]') }}" method="post" enctype="multipart/form-data"> <form action="{{ url_for('create_[[ name|camel_to_snake ]]') }}" method="post" enctype="multipart/form-data">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<input type="hidden" name="_next" value="{{ _next or request.args.get('_next') or url_for('list_[[ name|camel_to_snake ]]') }}"/> <input type="hidden" name="_next" value="{{ _next or request.args.get('_next') or url_for('list_[[ name|camel_to_snake ]]') }}"/>
<table> <table>
[%- for column in columns %] [%- for column in columns %]

View File

@ -1,4 +1,5 @@
<form action="{{ url_for('update_[[ name|camel_to_snake ]]', uuid=instance.id) }}" method="post" enctype="multipart/form-data"> <form action="{{ url_for('update_[[ name|camel_to_snake ]]', uuid=instance.id) }}" method="post" enctype="multipart/form-data">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<input type="hidden" name="_next" value="{{ _next or request.args.get('_next') or url_for('get_[[ name|camel_to_snake ]]', uuid=instance.id) }}"/> <input type="hidden" name="_next" value="{{ _next or request.args.get('_next') or url_for('get_[[ name|camel_to_snake ]]', uuid=instance.id) }}"/>
<table> <table>
[%- for column in columns %] [%- for column in columns %]

View File

@ -15,5 +15,6 @@
<a href="{{ url_for('security.logout') }}">{{ _("Logout") }}</a> | <a href="{{ url_for('security.logout') }}">{{ _("Logout") }}</a> |
{% else %} {% else %}
<a href="{{ url_for('security.login') }}">{{ _("Login") }}</a> | <a href="{{ url_for('security.login') }}">{{ _("Login") }}</a> |
<a href="{{ url_for('oshipka_bp.sso') }}">{{ _("SSO") }}</a>
{% endif %} {% endif %}
</div> </div>

View File

@ -0,0 +1,21 @@
from sqlalchemy_utils import ChoiceType
from oshipka.persistance import db, ModelController
from oshipka.persistance import SHARING_TYPE_TYPES, SHARING_TYPE_TYPES_TYPE_PUBLIC
class [[ name ]]Acl(db.Model, ModelController):
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
user = db.relationship('User', backref=db.backref("[[ name|camel_to_snake ]]_acls"))
instance_id = db.Column(db.Integer, db.ForeignKey('[[ name|camel_to_snake ]].id'))
instance = db.relationship('[[ name ]]', backref=db.backref("[[ name|camel_to_snake|pluralize ]]"))
acl_type = db.Column(ChoiceType(SHARING_TYPE_TYPES), default=SHARING_TYPE_TYPES_TYPE_PUBLIC)
_read = db.Column(db.Boolean, default=True)
_update = db.Column(db.Boolean, default=True)
_delete = db.Column(db.Boolean, default=True)
[% for column in columns %]
[[ column.name ]]__read = db.Column(db.Boolean, default=True)
[[ column.name ]]__write = db.Column(db.Boolean, default=True)
[%- endfor %]