initial commit
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
adinfo.yaml
|
||||
BIN
__pycache__/ad.cpython-312.pyc
Normal file
BIN
__pycache__/ad.cpython-312.pyc
Normal file
Binary file not shown.
BIN
__pycache__/db.cpython-312.pyc
Normal file
BIN
__pycache__/db.cpython-312.pyc
Normal file
Binary file not shown.
BIN
__pycache__/mfa.cpython-312.pyc
Normal file
BIN
__pycache__/mfa.cpython-312.pyc
Normal file
Binary file not shown.
BIN
__pycache__/yamlcon.cpython-312.pyc
Normal file
BIN
__pycache__/yamlcon.cpython-312.pyc
Normal file
Binary file not shown.
174
accounts.py
Normal file
174
accounts.py
Normal file
@@ -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 = """
|
||||
# <h1>Welcome, {{ current_user.data.givenName }}</h1>
|
||||
|
||||
# <h2> Email: {{ current_user.data.mail }}</h2>
|
||||
# <h2>{{ current_user.dn }}</h2>
|
||||
# """
|
||||
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 method="POST">
|
||||
<label>Username{{ form.username() }}</label>
|
||||
<label>Password{{ form.password() }}</label>
|
||||
{{ form.submit() }}
|
||||
{{ form.hidden_tag() }}
|
||||
</form>
|
||||
"""
|
||||
|
||||
# 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()
|
||||
8
ad.py
Normal file
8
ad.py
Normal file
@@ -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)
|
||||
66
db.py
Normal file
66
db.py
Normal file
@@ -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')}")
|
||||
14
mfa.py
Normal file
14
mfa.py
Normal file
@@ -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()
|
||||
18
templates/2fa.html
Normal file
18
templates/2fa.html
Normal file
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Accounts</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>{{ currSecret }}</h1>
|
||||
<h1>{{ code }}</h1>
|
||||
<a href="{{ homeurl }}">
|
||||
<button>Back home</button>
|
||||
</a>
|
||||
<a href="{{ delurl }}">
|
||||
<button>Disable MFA</button>
|
||||
</a>
|
||||
</body>
|
||||
</html>
|
||||
19
templates/home.html
Normal file
19
templates/home.html
Normal file
@@ -0,0 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Accounts</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Welcome, {{ current_user.data.givenName }}</h1>
|
||||
<h2>Email: {{ current_user.data.mail }}</h2>
|
||||
<h2>{{ current_user.dn }}</h2>
|
||||
<a href="{{ mfaurl }}">
|
||||
<button>MFA</button>
|
||||
</a>
|
||||
<a href="http://accounts.bbrunson.com">
|
||||
<button>Change Password</button>
|
||||
</a>
|
||||
</body>
|
||||
</html>
|
||||
10
yamlcon.py
Normal file
10
yamlcon.py
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user