commit 4a6257ec6c1eeb7df9758b79065fcb34a2113529 Author: brandon Date: Thu Feb 22 13:40:14 2024 -0800 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..79cfe50 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +adinfo.yaml diff --git a/__pycache__/ad.cpython-312.pyc b/__pycache__/ad.cpython-312.pyc new file mode 100644 index 0000000..ec1aa82 Binary files /dev/null and b/__pycache__/ad.cpython-312.pyc differ diff --git a/__pycache__/db.cpython-312.pyc b/__pycache__/db.cpython-312.pyc new file mode 100644 index 0000000..8e65788 Binary files /dev/null and b/__pycache__/db.cpython-312.pyc differ diff --git a/__pycache__/mfa.cpython-312.pyc b/__pycache__/mfa.cpython-312.pyc new file mode 100644 index 0000000..c1ed053 Binary files /dev/null and b/__pycache__/mfa.cpython-312.pyc differ diff --git a/__pycache__/yamlcon.cpython-312.pyc b/__pycache__/yamlcon.cpython-312.pyc new file mode 100644 index 0000000..724f3db Binary files /dev/null and b/__pycache__/yamlcon.cpython-312.pyc differ diff --git a/accounts.py b/accounts.py new file mode 100644 index 0000000..fc1a84d --- /dev/null +++ b/accounts.py @@ -0,0 +1,174 @@ +from flask import Flask, url_for +from flask_ldap3_login import LDAP3LoginManager +from flask_login import LoginManager, login_user, UserMixin, current_user +from flask import render_template_string, redirect, render_template +from flask_ldap3_login.forms import LDAPLoginForm +from mfa import generateOTP, generateSecret +# from ad import updateMfaSecret +from db import setupMfaSecret, updateMfaSecret, getMfaSecret, removeMfa +import yamlcon + +app = Flask(__name__) +app.config['SECRET_KEY'] = 'secret' +app.config['DEBUG'] = True + +# Setup LDAP Configuration Variables. Change these to your own settings. +# All configuration directives can be found in the documentation. + +# Hostname of your LDAP Server +app.config['LDAP_HOST'] = 'corp.bbrunson.com' + +app.config['LDAP_PORT'] = 389 + +# Base DN of your directory +app.config['LDAP_BASE_DN'] = 'dc=corp,dc=bbrunson,dc=com' + +# Users DN to be prepended to the Base DN +app.config['LDAP_USER_DN'] = 'cn=users' + +# Groups DN to be prepended to the Base DN +# app.config['LDAP_GROUP_DN'] = 'cn=groups' + +# The RDN attribute for your user schema on LDAP +app.config['LDAP_USER_RDN_ATTR'] = 'cn' + +# The Attribute you want users to authenticate to LDAP with. +app.config['LDAP_USER_LOGIN_ATTR'] = 'sAMAccountName' + +adinfo = yamlcon.load("adinfo.yaml") + +# The Username to bind to LDAP with +app.config['LDAP_BIND_USER_DN'] = adinfo.get('adbind_user', '') + +# The Password to bind to LDAP with +app.config['LDAP_BIND_USER_PASSWORD'] = adinfo.get('adbind_pass', '') + +login_manager = LoginManager(app) # Setup a Flask-Login Manager +ldap_manager = LDAP3LoginManager(app) # Setup a LDAP3 Login Manager. + +# Create a dictionary to store the users in when they authenticate +# This example stores users in memory. +users = {} + + +# Declare an Object Model for the user, and make it comply with the +# flask-login UserMixin mixin. +class User(UserMixin): + def __init__(self, dn, username, data): + self.dn = dn + self.username = username + self.data = data + + def __repr__(self): + return self.dn + + def get_id(self): + return self.dn + + +# Declare a User Loader for Flask-Login. +# Simply returns the User if it exists in our 'database', otherwise +# returns None. +@login_manager.user_loader +def load_user(id): + if id in users: + return users[id] + return None + + +# Declare The User Saver for Flask-Ldap3-Login +# This method is called whenever a LDAPLoginForm() successfully validates. +# Here you have to save the user, and return it so it can be used in the +# login controller. +@ldap_manager.save_user +def save_user(dn, username, data, memberships): + user = User(dn, username, data) + users[dn] = user + return user + + +# Declare some routes for usage to show the authentication process. +@app.route('/') +def home(): + # Redirect users who are not logged in. + if not current_user or current_user.is_anonymous: + return redirect(url_for('login')) + + # User is logged in, so show them a page with their cn and dn. + # template = """ + #

Welcome, {{ current_user.data.givenName }}

+ + #

Email: {{ current_user.data.mail }}

+ #

{{ current_user.dn }}

+ # """ + print(current_user) + print(current_user.data) + print(current_user.data['objectSid']) + + # return render_template_string(template) + return render_template('home.html', current_user=current_user, mfaurl=url_for('two_factor')) + +@app.route('/2fa') +def two_factor(): + if not current_user or current_user.is_anonymous: + return redirect(url_for('login')) + + if getMfaSecret(user=current_user.data['sAMAccountName']): + currSecret = "MFA already setup" + code = generateOTP(getMfaSecret(user=current_user.data['sAMAccountName'])) + + elif not getMfaSecret(user=current_user.data['sAMAccountName']): + setupMfaSecret(user=current_user.data['sAMAccountName'], secret=generateSecret()) + # currSecret = current_user.data['mfaSecret'] + currSecret = getMfaSecret(user=current_user.data['sAMAccountName']) + code = '' + + print(currSecret) + return render_template('2fa.html', currSecret=currSecret, code=code, homeurl=url_for('home'), delurl=url_for('delTwoFactor')) + +@app.route('/del2fa') +def delTwoFactor(): + if not current_user or current_user.is_anonymous: + return redirect(url_for('login')) + + removeMfa(user=current_user.data['sAMAccountName']) + + return redirect(url_for('home')) + +@app.route('/manual_login') +def manual_login(): + # Instead of using the form, you can alternatively authenticate + # using the authenticate method. + # This WILL NOT fire the save_user() callback defined above. + # You are responsible for saving your users. + app.ldap3_login_manager.authenticate('username', 'password') + + +@app.route('/login', methods=['GET', 'POST']) +def login(): + template = """ + {{ get_flashed_messages() }} + {{ form.errors }} +
+ + + {{ form.submit() }} + {{ form.hidden_tag() }} +
+ """ + + # Instantiate a LDAPLoginForm which has a validator to check if the user + # exists in LDAP. + form = LDAPLoginForm() + + if form.validate_on_submit(): + # Successfully logged in, We can now access the saved user object + # via form.user. + login_user(form.user) # Tell flask-login to log them in. + return redirect('/') # Send them home + + return render_template_string(template, form=form) + + +if __name__ == '__main__': + app.run() \ No newline at end of file diff --git a/ad.py b/ad.py new file mode 100644 index 0000000..991610d --- /dev/null +++ b/ad.py @@ -0,0 +1,8 @@ +from ms_active_directory import ADDomain + +def updateMfaSecret(user, secret): + domain = ADDomain('corp.bbrunson.com') + session = domain.create_session_as_user('administrator@bbrunson.com', 'Mariposa2502$$$$') + + success = session.overwrite_attribute_for_user(user, 'mfaSecret', + secret) \ No newline at end of file diff --git a/db.py b/db.py new file mode 100644 index 0000000..8b2d221 --- /dev/null +++ b/db.py @@ -0,0 +1,66 @@ +import sqlite3 + +# Function to add a new user to the database +def setupMfaSecret(user, secret): + with sqlite3.connect('mfa.db') as conn: + cursor = conn.cursor() + cursor.execute(''' + INSERT INTO users (user, secret) + VALUES (?, ?) + ''', (user, secret)) + conn.commit() + +# Function to retrieve the number corresponding to a username +def getMfaSecret(user): + with sqlite3.connect('mfa.db') as conn: + cursor = conn.cursor() + cursor.execute(''' + SELECT secret FROM users + WHERE user = ? + ''', (user,)) + result = cursor.fetchone() + if result: + return result[0] + else: + return None + +# Function to update the number for a specific username +def updateMfaSecret(user, secret): + with sqlite3.connect('mfa.db') as conn: + cursor = conn.cursor() + cursor.execute(''' + UPDATE users + SET secret = ? + WHERE user = ? + ''', (secret, user)) + conn.commit() + +def removeMfa(user): + with sqlite3.connect('mfa.db') as conn: + cursor = conn.cursor() + cursor.execute(''' + DELETE FROM users + WHERE user = ? + ''', (user,)) + conn.commit() + +def initializeDb(): + with sqlite3.connect('mfa.db') as conn: + cursor = conn.cursor() + cursor.execute(''' + CREATE TABLE IF NOT EXISTS users ( + user TEXT PRIMARY KEY, + secret TEXT + ) + ''') + +# initializeDb() + +# Example usage +# add_user('user1', 123) +# add_user('user2', 456) + +# print(f"Number for 'user1': {get_number('user1')}") + +# update_number('user1', 789) +# print(f"Updated number for 'user1': {get_number('user1')}") diff --git a/mfa.db b/mfa.db new file mode 100644 index 0000000..cdcafc0 Binary files /dev/null and b/mfa.db differ diff --git a/mfa.py b/mfa.py new file mode 100644 index 0000000..9d03d95 --- /dev/null +++ b/mfa.py @@ -0,0 +1,14 @@ +import pyotp + +def generateOTP(secret): + totp = pyotp.TOTP(secret) + code = totp.now() + print(code) + return code + +# def checkOTP(secret, code): +# totp = pyotp.TOTP(secret) +# return totp.verify(code) + +def generateSecret(): + return pyotp.random_base32() \ No newline at end of file diff --git a/templates/2fa.html b/templates/2fa.html new file mode 100644 index 0000000..d4454da --- /dev/null +++ b/templates/2fa.html @@ -0,0 +1,18 @@ + + + + + + Accounts + + +

{{ currSecret }}

+

{{ code }}

+ + + + + + + + \ No newline at end of file diff --git a/templates/home.html b/templates/home.html new file mode 100644 index 0000000..000b2b0 --- /dev/null +++ b/templates/home.html @@ -0,0 +1,19 @@ + + + + + + Accounts + + +

Welcome, {{ current_user.data.givenName }}

+

Email: {{ current_user.data.mail }}

+

{{ current_user.dn }}

+ + + + + + + + \ No newline at end of file diff --git a/yamlcon.py b/yamlcon.py new file mode 100644 index 0000000..d8ac5b9 --- /dev/null +++ b/yamlcon.py @@ -0,0 +1,10 @@ +import yaml + +def load(path): + with open(path, 'r') as file: + try: + data = yaml.safe_load(file) + return data + except yaml.YAMLError as e: + print(f"Error reading YAML file {path}: {e}") + return None \ No newline at end of file