more ldap migration

This commit is contained in:
TuxCoder 2022-06-18 19:35:05 +02:00
parent 927562fecb
commit c6042973fe
7 changed files with 55 additions and 31 deletions

View file

@ -1,7 +1,8 @@
from flask import current_app
from flask_wtf import FlaskForm
from .form.auth import PasswordForm, TotpForm, Fido2Form
from ldap3 import Server, Connection, HASHED_SALTED_SHA256
from ldap3.core.exceptions import LDAPException
from hmac import compare_digest as compare_hash
import crypt
from .model import User
import logging
@ -17,11 +18,11 @@ class AuthProvider:
return csl.__name__
@staticmethod
def get_form():
def get_form() -> FlaskForm:
return
@staticmethod
def check_auth(user, form) -> bool:
def check_auth(user: User, form) -> bool:
'''
checks the submited form is valid
return true if user is allowed to auth
@ -29,30 +30,26 @@ class AuthProvider:
return False
class LdapAuthProvider(AuthProvider):
class PasswordAuthProvider(AuthProvider):
@staticmethod
def get_form():
def get_form() -> FlaskForm:
return PasswordForm(prefix='password')
@staticmethod
def check_auth(user: User, form):
return LdapAuthProvider.check_auth_internal(
user, form.data['password'])
@staticmethod
def check_auth_internal(user, password):
server = Server(current_app.config['LDAP_URL'])
ldap_conn = Connection(server, user.entry_dn, password)
try:
return ldap_conn.bind()
except LDAPException:
def check_auth(user: User, form: FlaskForm) -> bool:
if isinstance(form.data['password'], str):
return PasswordAuthProvider.check_auth_internal(user, form.data['password'])
else:
return False
@staticmethod
def check_auth_internal(user: User, password: str) -> bool:
return compare_hash(crypt.crypt(password, user.password_hashed),user.password_hashed)
class U2FAuthProvider(AuthProvider):
@staticmethod
def get_from():
def get_from() -> FlaskForm:
return Fido2Form(prefix='fido2')
@ -67,7 +64,7 @@ class TotpAuthProvider(AuthProvider):
return TotpForm(prefix='totp')
@staticmethod
def check_auth(user, form):
def check_auth(user: User, form: FlaskForm) -> bool:
data = form.data['totp']
if data is not None:
#print(f'data totp: {data}')
@ -80,7 +77,7 @@ class TotpAuthProvider(AuthProvider):
AUTH_PROVIDER_LIST = [
LdapAuthProvider,
PasswordAuthProvider,
TotpAuthProvider
]

View file

@ -7,6 +7,12 @@ Create Date: 2022-06-17 13:15:33.450531
"""
from alembic import op
import sqlalchemy as sa
from flask import current_app
from lenticular_cloud.model import User
from ldap3_orm import AttrDef, EntryBase as _EntryBase, ObjectDef, EntryType
from ldap3_orm import Reader
from ldap3 import Connection, Server, ALL
import logging
# revision identifiers, used by Alembic.
@ -17,6 +23,14 @@ depends_on = None
def upgrade():
app = current_app
server = Server(app.config['LDAP_URL'], get_info=ALL)
ldap_conn = Connection(server, app.config['LDAP_BIND_DN'], app.config['LDAP_BIND_PW'], auto_bind=True) # TODO auto_bind read docu
base_dn = app.config['LDAP_BASE_DN']
object_def = ObjectDef(["inetOrgPerson"], ldap_conn)
user_base_dn = f"ou=users,{base_dn}"
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('app_token',
sa.Column('id', sa.Integer(), nullable=False),
@ -36,8 +50,21 @@ def upgrade():
op.add_column('user', sa.Column('enabled', sa.Boolean(), server_default="false", nullable=True))
# ### end Alembic commands ###
op.execute("UPDATE `user` SET enabled= 1;")
#op.execute('UPDATE `user` SET password_hashed = "";')
op.execute(User.__table__.update().values({'enabled': True}))
conn = op.get_bind()
users = conn.execute(User.__table__.select())
for user in users:
print(f"migrating user {user.username}")
reader = Reader(ldap_conn, object_def, user_base_dn, f'(uid={user.username})')
result = reader.search()
if len(result) == 0:
print(f"WARNING: could not migrate user {user.username}")
continue
ldap_object = result[0]
password_hashed = ldap_object.userPassword[0].decode().replace('{CRYPT}','')
op.execute(User.__table__.update().values({'password_hashed': password_hashed}).where(User.id == user.id))
def downgrade():

View file

@ -49,7 +49,7 @@ class Service(object):
}
@staticmethod
def from_config(name, config) -> Service:
def from_config(name, config) -> 'Service':
"""
"""
service = Service(name)
@ -189,7 +189,7 @@ class Totp(BaseModel):
secret = db.Column(db.String, nullable=False)
name = db.Column(db.String, nullable=False)
created_at = db.Column(db.DateTime, default=datetime.now, nullable=False)
last_used = db.Column(db.DateTime, nullable=True)
#last_used = db.Column(db.DateTime, nullable=True)
user_id = db.Column(
db.String(length=36),

View file

@ -11,7 +11,6 @@ import logging
import httpx
from ..model import User
from ..auth_providers import LdapAuthProvider
from ..hydra import hydra_service
from ory_hydra_client.api.admin import introspect_o_auth_2_token
from ory_hydra_client.models import GenericError
@ -37,7 +36,7 @@ def user_list() -> ResponseReturnValue:
return jsonify([
{'username': str(user.username), 'email': str(user.email)}
for user in User.query_().all()])
for user in User.query.all()])
@api_views.route('/introspect', methods=['POST'])
def introspect() -> ResponseReturnValue:
@ -66,7 +65,7 @@ def email_login() -> ResponseReturnValue:
if not request.is_json:
return jsonify({}), 400
req_payload = request.get_json()
logger.error(f'{req_payload}')
logger.debug(f'{req_payload}')
if not isinstance(req_payload, dict):
return 'bad request', 400
password = req_payload["password"]

View file

@ -147,6 +147,7 @@ async def login_auth() -> ResponseReturnValue:
if auth_provider.get_name() not in session['auth_providers'] and\
auth_provider.check_auth(user, form):
session['auth_providers'].append(auth_provider.get_name())
session.modified = True
if auth_provider.get_name() not in session['auth_providers']:
auth_forms[auth_provider.get_name()]=form

View file

@ -27,7 +27,7 @@ from ..model import db, User, SecurityUser, Totp, WebauthnCredential
from ..form.frontend import ClientCertForm, TOTPForm, \
TOTPDeleteForm, PasswordChangeForm, WebauthnRegisterForm
from ..form.base import ButtonForm
from ..auth_providers import LdapAuthProvider
from ..auth_providers import PasswordAuthProvider
from .auth import webauthn
from .oauth2 import redirect_login, oauth2
from ..hydra import hydra_service
@ -275,7 +275,7 @@ def password_change_post() -> ResponseReturnValue:
if form.validate():
password_old = str(form.data['password_old'])
password_new = str(form.data['password_new'])
if not LdapAuthProvider.check_auth_internal(
if not PasswordAuthProvider.check_auth_internal(
current_user, password_old):
return jsonify(
{'errors': {'password_old': 'Old Password is invalid'}})

View file

@ -28,8 +28,8 @@ def redirect_login() -> ResponseReturnValue:
session['next_url'] = request.path
redirect_uri = url_for('oauth2.authorized', _external=True)
response = oauth2.custom.authorize_redirect(redirect_uri)
#if isinstance(response, ResponseReturnValue):
# raise RuntimeError("invalid redirect")
if isinstance(response, Response):
raise RuntimeError("invalid redirect")
return response