diff --git a/bootstrap/data_static/Role.csv b/bootstrap/data_static/Role.csv deleted file mode 100644 index fa554c4..0000000 --- a/bootstrap/data_static/Role.csv +++ /dev/null @@ -1,2 +0,0 @@ -name -admin \ No newline at end of file diff --git a/bootstrap/data_static/User.csv b/bootstrap/data_static/User.csv deleted file mode 100644 index 0d9f71c..0000000 --- a/bootstrap/data_static/User.csv +++ /dev/null @@ -1,2 +0,0 @@ -email,password,role_names -admin@app,__SENSITIVE__.ADMIN_PASSWORD,admin \ No newline at end of file diff --git a/bootstrap/data_static/_process_order b/bootstrap/data_static/_process_order index 942a1ea..e69de29 100644 --- a/bootstrap/data_static/_process_order +++ b/bootstrap/data_static/_process_order @@ -1,2 +0,0 @@ -Role -User \ No newline at end of file diff --git a/bootstrap/sensitive_dev.py b/bootstrap/sensitive_dev.py index 85109ed..b2c3864 100644 --- a/bootstrap/sensitive_dev.py +++ b/bootstrap/sensitive_dev.py @@ -1,3 +1,2 @@ -ADMIN_PASSWORD = "password" SSO_CLIENT_ID = '123456' SSO_CLIENT_SECRET = 'secret' \ No newline at end of file diff --git a/bootstrap/webapp/templates/navigation.html b/bootstrap/webapp/templates/navigation.html index 06ea396..5e36469 100644 --- a/bootstrap/webapp/templates/navigation.html +++ b/bootstrap/webapp/templates/navigation.html @@ -4,6 +4,6 @@ {{ current_user.email }} | {{ _("Logout") }} | {% else %} - {{ _("Login") }} | + {{ _("SSO Login") }} | {% endif %} \ No newline at end of file diff --git a/oshipka/persistance/__init__.py b/oshipka/persistance/__init__.py index 3ace074..b58348f 100644 --- a/oshipka/persistance/__init__.py +++ b/oshipka/persistance/__init__.py @@ -1,4 +1,3 @@ -import base64 import csv import datetime import json @@ -9,7 +8,6 @@ from importlib import import_module from json import JSONEncoder from uuid import uuid4 -import onetimepass from flask import request from flask_migrate import Migrate from flask_migrate import init as migrate_init @@ -17,7 +15,6 @@ from flask_migrate import upgrade as migrate_upgrade from flask_security import RoleMixin, UserMixin from flask_security import Security, SQLAlchemyUserDatastore from flask_security import current_user -from flask_security.utils import hash_password from flask_sqlalchemy import SQLAlchemy from flask_wtf import CSRFProtect from markupsafe import escape, Markup @@ -27,7 +24,6 @@ from sqlalchemy.ext.declarative import declared_attr, DeclarativeMeta from sqlalchemy.orm.collections import InstrumentedList from sqlalchemy_utils import Choice 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 config import SQLALCHEMY_DATABASE_URI, MAKEDIRS, DATABASE_FILE, SEARCH_INDEX_PATH, STATIC_DATA_DIR, MEDIA_DIR, \ @@ -69,6 +65,7 @@ roles_users = db.Table('roles_users', db.Column('user_id', db.Integer(), db.ForeignKey('user.id')), db.Column('role_id', db.Integer(), db.ForeignKey('role.id'))) + class ModelJsonEncoder(JSONEncoder): def default(self, o): if isinstance(o, datetime.datetime): @@ -177,11 +174,10 @@ class Role(db.Model, ModelController, RoleMixin): class User(db.Model, ModelController, UserMixin): - email = db.Column(db.Unicode, unique=True) - password = db.Column(db.Unicode) + username = db.Column(db.Unicode, unique=True) + token = db.Column(db.Unicode) active = db.Column(db.Boolean(), default=True) - confirmed_at = db.Column(db.DateTime()) timezone = db.Column(db.String, default='UTC') tz_offset_seconds = db.Column(db.Integer, default=0) @@ -194,49 +190,6 @@ class User(db.Model, ModelController, UserMixin): backref=db.backref('users', lazy='dynamic')) -class Credential(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_s.id')) - user_sso = db.relationship('UserS', - backref=db.backref("credentials"), - ) - - -class UserS(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(UserS, 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() user_datastore = SQLAlchemyUserDatastore(db, User, Role) @@ -437,7 +390,6 @@ def populate_static(app): role_names = row.pop('role_names') else: role_names = "" - row['password'] = hash_password(row['password']) user = user_datastore.create_user(**row) for role_name in role_names.split(';'): role = Role.query.filter_by(name=role_name).first() diff --git a/oshipka/webapp/default_routes.py b/oshipka/webapp/default_routes.py index 92a0689..36b3314 100644 --- a/oshipka/webapp/default_routes.py +++ b/oshipka/webapp/default_routes.py @@ -2,9 +2,11 @@ import urllib import requests from flask import send_from_directory, redirect, request, url_for, session, jsonify +from flask_security import login_user +from oshipka.persistance import User, db from oshipka.util.strings import random_string_generator -from oshipka.webapp import oshipka_bp +from oshipka.webapp import oshipka_bp, app from config import MEDIA_DIR, APP_BASE_URL from sensitive import SSO_CLIENT_ID, SSO_CLIENT_SECRET @@ -15,16 +17,17 @@ def get_media(filepath): return send_from_directory(MEDIA_DIR, filepath) -SSO_BASE_URL = 'http://localhost:5008' +SSO_BASE_URL = 'http://sso.localhost:5008' SSO_AUTH_URL = '/oidc/auth' SSO_TOKEN_URL = '/oidc/token' SSO_USERINFO_URL = "/endpoints/userinfo" +@app.route('/login') @oshipka_bp.route('/sso') def sso(): callback_url = APP_BASE_URL + url_for('oshipka_bp.oidc_callback') - state = random_string_generator() + state = request.referrer or url_for('home') session['oidc_state'] = state params = urllib.parse.urlencode({ 'redirect_uri': callback_url, @@ -66,6 +69,17 @@ def oidc_callback(): }, ) if response.status_code == 200: - return response.json() - return 'got code for userinfo: {}'.format(response.status_code) - return 'got response for token: {}'.format(response.status_code) + response_json = response.json() + username = response_json.get('user', {}).get('username') + user = User.query.filter_by(username=username).first() + redirect_uri = url_for('home') + if not user: + user = User(username=username) + db.session.add(user) + db.session.commit() + login_user(user) + if 'oidc_state' in session: + redirect_uri = session['oidc_state'] + del session['oidc_state'] + return redirect(redirect_uri) + return response.json() diff --git a/requirements.txt b/requirements.txt index 847efae..03f7ffe 100644 --- a/requirements.txt +++ b/requirements.txt @@ -24,7 +24,6 @@ inflect==4.1.0 itsdangerous==1.1.0 Jinja2==2.11.1 MarkupSafe==1.1.1 -onetimepass==1.0.1 passlib==1.7.2 pathtools==0.1.2 pycparser==2.20 diff --git a/vm_gen/templates/_model_py b/vm_gen/templates/_model_py index 34040dd..a41d5c4 100644 --- a/vm_gen/templates/_model_py +++ b/vm_gen/templates/_model_py @@ -2,6 +2,10 @@ from sqlalchemy_utils import ChoiceType [%- endif %] +[%- if _password_types %] +from werkzeug.security import generate_password_hash +[%- endif %] + class [[ name ]](db.Model, ModelController[% for inherit in interits %], [[ inherit ]][% endfor %]): [%- include "_model_choice_header_py" %] [%- include "_model_searchable_header_py" %] @@ -17,6 +21,17 @@ class [[ name ]](db.Model, ModelController[% for inherit in interits %], [[ inhe [[ column.name ]] = db.Column([[ column._type ]], [%- if column.default %]default="[[ column.default ]]",[%- endif %] [%- if column.index %]index=True,[%- endif %]) + [%- if column.type == 'password' %] + + @property + def [[ column.name ]]__password(self): + raise AttributeError('password is not a readable attribute') + + @[[ column.name ]]__password.setter + def [[ column.name ]]__password(self, password): + self.password_hash = generate_password_hash(password) + + [% endif %] [%- endif %] [%- endfor %] diff --git a/vm_gen/templates/html/navigation.html b/vm_gen/templates/html/navigation.html index c0912b5..07660e8 100644 --- a/vm_gen/templates/html/navigation.html +++ b/vm_gen/templates/html/navigation.html @@ -11,10 +11,9 @@
{% if current_user.is_authenticated %} - {{ current_user.email }} | + {{ current_user.username }} | {{ _("Logout") }} | {% else %} - {{ _("Login") }} | - {{ _("SSO") }} + {{ _("Login SSO") }} {% endif %}
\ No newline at end of file diff --git a/vm_gen/vm_gen.py b/vm_gen/vm_gen.py index 6fd4953..0d41c11 100644 --- a/vm_gen/vm_gen.py +++ b/vm_gen/vm_gen.py @@ -94,6 +94,9 @@ def enrich_view_model(view_model): column_type = column.get('type') if column_type in ['text', 'long_text', ]: _column_type = 'db.UnicodeText' + elif column_type in ['password', ]: + _column_type = 'db.UnicodeText' + view_model['_password_types'] = True elif column_type in ['number', 'int', 'integer', ]: _column_type = 'db.Integer' elif column_type in ['bool', 'boolean', ] or column_name.startswith('is_'):