more ldap migration
This commit is contained in:
parent
927562fecb
commit
c6042973fe
|
@ -1,7 +1,8 @@
|
||||||
from flask import current_app
|
from flask import current_app
|
||||||
|
from flask_wtf import FlaskForm
|
||||||
from .form.auth import PasswordForm, TotpForm, Fido2Form
|
from .form.auth import PasswordForm, TotpForm, Fido2Form
|
||||||
from ldap3 import Server, Connection, HASHED_SALTED_SHA256
|
from hmac import compare_digest as compare_hash
|
||||||
from ldap3.core.exceptions import LDAPException
|
import crypt
|
||||||
from .model import User
|
from .model import User
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
@ -17,11 +18,11 @@ class AuthProvider:
|
||||||
return csl.__name__
|
return csl.__name__
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_form():
|
def get_form() -> FlaskForm:
|
||||||
return
|
return
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def check_auth(user, form) -> bool:
|
def check_auth(user: User, form) -> bool:
|
||||||
'''
|
'''
|
||||||
checks the submited form is valid
|
checks the submited form is valid
|
||||||
return true if user is allowed to auth
|
return true if user is allowed to auth
|
||||||
|
@ -29,30 +30,26 @@ class AuthProvider:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
class LdapAuthProvider(AuthProvider):
|
class PasswordAuthProvider(AuthProvider):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_form():
|
def get_form() -> FlaskForm:
|
||||||
return PasswordForm(prefix='password')
|
return PasswordForm(prefix='password')
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def check_auth(user: User, form):
|
def check_auth(user: User, form: FlaskForm) -> bool:
|
||||||
return LdapAuthProvider.check_auth_internal(
|
if isinstance(form.data['password'], str):
|
||||||
user, form.data['password'])
|
return PasswordAuthProvider.check_auth_internal(user, form.data['password'])
|
||||||
|
else:
|
||||||
@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:
|
|
||||||
return False
|
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):
|
class U2FAuthProvider(AuthProvider):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_from():
|
def get_from() -> FlaskForm:
|
||||||
return Fido2Form(prefix='fido2')
|
return Fido2Form(prefix='fido2')
|
||||||
|
|
||||||
|
|
||||||
|
@ -67,7 +64,7 @@ class TotpAuthProvider(AuthProvider):
|
||||||
return TotpForm(prefix='totp')
|
return TotpForm(prefix='totp')
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def check_auth(user, form):
|
def check_auth(user: User, form: FlaskForm) -> bool:
|
||||||
data = form.data['totp']
|
data = form.data['totp']
|
||||||
if data is not None:
|
if data is not None:
|
||||||
#print(f'data totp: {data}')
|
#print(f'data totp: {data}')
|
||||||
|
@ -80,7 +77,7 @@ class TotpAuthProvider(AuthProvider):
|
||||||
|
|
||||||
|
|
||||||
AUTH_PROVIDER_LIST = [
|
AUTH_PROVIDER_LIST = [
|
||||||
LdapAuthProvider,
|
PasswordAuthProvider,
|
||||||
TotpAuthProvider
|
TotpAuthProvider
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,12 @@ Create Date: 2022-06-17 13:15:33.450531
|
||||||
"""
|
"""
|
||||||
from alembic import op
|
from alembic import op
|
||||||
import sqlalchemy as sa
|
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.
|
# revision identifiers, used by Alembic.
|
||||||
|
@ -17,6 +23,14 @@ depends_on = None
|
||||||
|
|
||||||
|
|
||||||
def upgrade():
|
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! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
op.create_table('app_token',
|
op.create_table('app_token',
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
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))
|
op.add_column('user', sa.Column('enabled', sa.Boolean(), server_default="false", nullable=True))
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
op.execute("UPDATE `user` SET enabled= 1;")
|
op.execute(User.__table__.update().values({'enabled': True}))
|
||||||
#op.execute('UPDATE `user` SET password_hashed = "";')
|
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():
|
def downgrade():
|
||||||
|
|
|
@ -49,7 +49,7 @@ class Service(object):
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_config(name, config) -> Service:
|
def from_config(name, config) -> 'Service':
|
||||||
"""
|
"""
|
||||||
"""
|
"""
|
||||||
service = Service(name)
|
service = Service(name)
|
||||||
|
@ -189,7 +189,7 @@ class Totp(BaseModel):
|
||||||
secret = db.Column(db.String, nullable=False)
|
secret = db.Column(db.String, nullable=False)
|
||||||
name = db.Column(db.String, nullable=False)
|
name = db.Column(db.String, nullable=False)
|
||||||
created_at = db.Column(db.DateTime, default=datetime.now, 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(
|
user_id = db.Column(
|
||||||
db.String(length=36),
|
db.String(length=36),
|
||||||
|
|
|
@ -11,7 +11,6 @@ import logging
|
||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
from ..model import User
|
from ..model import User
|
||||||
from ..auth_providers import LdapAuthProvider
|
|
||||||
from ..hydra import hydra_service
|
from ..hydra import hydra_service
|
||||||
from ory_hydra_client.api.admin import introspect_o_auth_2_token
|
from ory_hydra_client.api.admin import introspect_o_auth_2_token
|
||||||
from ory_hydra_client.models import GenericError
|
from ory_hydra_client.models import GenericError
|
||||||
|
@ -37,7 +36,7 @@ def user_list() -> ResponseReturnValue:
|
||||||
|
|
||||||
return jsonify([
|
return jsonify([
|
||||||
{'username': str(user.username), 'email': str(user.email)}
|
{'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'])
|
@api_views.route('/introspect', methods=['POST'])
|
||||||
def introspect() -> ResponseReturnValue:
|
def introspect() -> ResponseReturnValue:
|
||||||
|
@ -66,7 +65,7 @@ def email_login() -> ResponseReturnValue:
|
||||||
if not request.is_json:
|
if not request.is_json:
|
||||||
return jsonify({}), 400
|
return jsonify({}), 400
|
||||||
req_payload = request.get_json()
|
req_payload = request.get_json()
|
||||||
logger.error(f'{req_payload}')
|
logger.debug(f'{req_payload}')
|
||||||
if not isinstance(req_payload, dict):
|
if not isinstance(req_payload, dict):
|
||||||
return 'bad request', 400
|
return 'bad request', 400
|
||||||
password = req_payload["password"]
|
password = req_payload["password"]
|
||||||
|
|
|
@ -147,6 +147,7 @@ async def login_auth() -> ResponseReturnValue:
|
||||||
if auth_provider.get_name() not in session['auth_providers'] and\
|
if auth_provider.get_name() not in session['auth_providers'] and\
|
||||||
auth_provider.check_auth(user, form):
|
auth_provider.check_auth(user, form):
|
||||||
session['auth_providers'].append(auth_provider.get_name())
|
session['auth_providers'].append(auth_provider.get_name())
|
||||||
|
session.modified = True
|
||||||
|
|
||||||
if auth_provider.get_name() not in session['auth_providers']:
|
if auth_provider.get_name() not in session['auth_providers']:
|
||||||
auth_forms[auth_provider.get_name()]=form
|
auth_forms[auth_provider.get_name()]=form
|
||||||
|
|
|
@ -27,7 +27,7 @@ from ..model import db, User, SecurityUser, Totp, WebauthnCredential
|
||||||
from ..form.frontend import ClientCertForm, TOTPForm, \
|
from ..form.frontend import ClientCertForm, TOTPForm, \
|
||||||
TOTPDeleteForm, PasswordChangeForm, WebauthnRegisterForm
|
TOTPDeleteForm, PasswordChangeForm, WebauthnRegisterForm
|
||||||
from ..form.base import ButtonForm
|
from ..form.base import ButtonForm
|
||||||
from ..auth_providers import LdapAuthProvider
|
from ..auth_providers import PasswordAuthProvider
|
||||||
from .auth import webauthn
|
from .auth import webauthn
|
||||||
from .oauth2 import redirect_login, oauth2
|
from .oauth2 import redirect_login, oauth2
|
||||||
from ..hydra import hydra_service
|
from ..hydra import hydra_service
|
||||||
|
@ -275,7 +275,7 @@ def password_change_post() -> ResponseReturnValue:
|
||||||
if form.validate():
|
if form.validate():
|
||||||
password_old = str(form.data['password_old'])
|
password_old = str(form.data['password_old'])
|
||||||
password_new = str(form.data['password_new'])
|
password_new = str(form.data['password_new'])
|
||||||
if not LdapAuthProvider.check_auth_internal(
|
if not PasswordAuthProvider.check_auth_internal(
|
||||||
current_user, password_old):
|
current_user, password_old):
|
||||||
return jsonify(
|
return jsonify(
|
||||||
{'errors': {'password_old': 'Old Password is invalid'}})
|
{'errors': {'password_old': 'Old Password is invalid'}})
|
||||||
|
|
|
@ -28,8 +28,8 @@ def redirect_login() -> ResponseReturnValue:
|
||||||
session['next_url'] = request.path
|
session['next_url'] = request.path
|
||||||
redirect_uri = url_for('oauth2.authorized', _external=True)
|
redirect_uri = url_for('oauth2.authorized', _external=True)
|
||||||
response = oauth2.custom.authorize_redirect(redirect_uri)
|
response = oauth2.custom.authorize_redirect(redirect_uri)
|
||||||
#if isinstance(response, ResponseReturnValue):
|
if isinstance(response, Response):
|
||||||
# raise RuntimeError("invalid redirect")
|
raise RuntimeError("invalid redirect")
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue