active directory authentication, full mfa implementation, vpn generation
This commit is contained in:
BIN
__pycache__/_forms.cpython-312.pyc
Normal file
BIN
__pycache__/_forms.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
__pycache__/vpn.cpython-312.pyc
Normal file
BIN
__pycache__/vpn.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
8
_forms.py
Normal file
8
_forms.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import wtforms
|
||||||
|
from ad import updatePassword
|
||||||
|
|
||||||
|
class ChangePasswordForm(wtforms.Form):
|
||||||
|
# oldpw = wtforms.PasswordField('OldPassword', validators=[wtforms.validators.DataRequired()])
|
||||||
|
newpw = wtforms.PasswordField('NewPassword', validators=[wtforms.validators.DataRequired(), wtforms.validators.EqualTo('conpw', message='Passwords must match')])
|
||||||
|
conpw = wtforms.PasswordField('ConPassword')
|
||||||
|
submit = wtforms.SubmitField('Submit')
|
||||||
83
accounts.py
83
accounts.py
@@ -1,12 +1,17 @@
|
|||||||
from flask import Flask, url_for
|
from flask import Flask, url_for
|
||||||
from flask_ldap3_login import LDAP3LoginManager
|
from flask_ldap3_login import LDAP3LoginManager
|
||||||
from flask_login import LoginManager, login_user, UserMixin, current_user
|
from flask_login import LoginManager, login_user, UserMixin, current_user, logout_user
|
||||||
from flask import render_template_string, redirect, render_template
|
from flask import render_template_string, redirect, render_template, request, send_file
|
||||||
from flask_ldap3_login.forms import LDAPLoginForm
|
from flask_ldap3_login.forms import LDAPLoginForm
|
||||||
from mfa import generateOTP, generateSecret
|
from mfa import generateOTP, generateSecret, generateProvisioningUri
|
||||||
# from ad import updateMfaSecret
|
# from ad import updateMfaSecret
|
||||||
from db import setupMfaSecret, updateMfaSecret, getMfaSecret, removeMfa
|
from db import setupMfaSecret, updateMfaSecret, getMfaSecret, removeMfa
|
||||||
import yamlcon
|
import yamlcon
|
||||||
|
from wtforms import StringField
|
||||||
|
from wtforms.validators import DataRequired
|
||||||
|
import _forms
|
||||||
|
from ad import updatePassword
|
||||||
|
from vpn import genVPN, getVPN
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.config['SECRET_KEY'] = 'secret'
|
app.config['SECRET_KEY'] = 'secret'
|
||||||
@@ -64,7 +69,9 @@ class User(UserMixin):
|
|||||||
|
|
||||||
def get_id(self):
|
def get_id(self):
|
||||||
return self.dn
|
return self.dn
|
||||||
|
|
||||||
|
class LDAPLoginForm0(LDAPLoginForm):
|
||||||
|
mfacode = StringField('MFA')
|
||||||
|
|
||||||
# Declare a User Loader for Flask-Login.
|
# Declare a User Loader for Flask-Login.
|
||||||
# Simply returns the User if it exists in our 'database', otherwise
|
# Simply returns the User if it exists in our 'database', otherwise
|
||||||
@@ -93,6 +100,11 @@ def home():
|
|||||||
# Redirect users who are not logged in.
|
# Redirect users who are not logged in.
|
||||||
if not current_user or current_user.is_anonymous:
|
if not current_user or current_user.is_anonymous:
|
||||||
return redirect(url_for('login'))
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
|
if getMfaSecret(user=current_user.data['sAMAccountName']):
|
||||||
|
mfaStatus = "MFA is active"
|
||||||
|
else:
|
||||||
|
mfaStatus = "MFA is not active"
|
||||||
|
|
||||||
# User is logged in, so show them a page with their cn and dn.
|
# User is logged in, so show them a page with their cn and dn.
|
||||||
# template = """
|
# template = """
|
||||||
@@ -101,12 +113,13 @@ def home():
|
|||||||
# <h2> Email: {{ current_user.data.mail }}</h2>
|
# <h2> Email: {{ current_user.data.mail }}</h2>
|
||||||
# <h2>{{ current_user.dn }}</h2>
|
# <h2>{{ current_user.dn }}</h2>
|
||||||
# """
|
# """
|
||||||
print(current_user)
|
# print(current_user)
|
||||||
print(current_user.data)
|
# print(current_user.data)
|
||||||
print(current_user.data['objectSid'])
|
# print(current_user.data['objectSid'])
|
||||||
|
|
||||||
# return render_template_string(template)
|
# return render_template_string(template)
|
||||||
return render_template('home.html', current_user=current_user, mfaurl=url_for('two_factor'))
|
return render_template('home.html', current_user=current_user, mfaurl=url_for('two_factor'), \
|
||||||
|
mfastatus=mfaStatus, logouturl=url_for('logout'), changepwurl=url_for('changepw'), vpnurl=url_for('vpn'))
|
||||||
|
|
||||||
@app.route('/2fa')
|
@app.route('/2fa')
|
||||||
def two_factor():
|
def two_factor():
|
||||||
@@ -121,10 +134,11 @@ def two_factor():
|
|||||||
setupMfaSecret(user=current_user.data['sAMAccountName'], secret=generateSecret())
|
setupMfaSecret(user=current_user.data['sAMAccountName'], secret=generateSecret())
|
||||||
# currSecret = current_user.data['mfaSecret']
|
# currSecret = current_user.data['mfaSecret']
|
||||||
currSecret = getMfaSecret(user=current_user.data['sAMAccountName'])
|
currSecret = getMfaSecret(user=current_user.data['sAMAccountName'])
|
||||||
|
uri = generateProvisioningUri(currSecret=currSecret, cu=current_user.data['sAMAccountName'])
|
||||||
code = ''
|
code = ''
|
||||||
|
|
||||||
print(currSecret)
|
print(currSecret)
|
||||||
return render_template('2fa.html', currSecret=currSecret, code=code, homeurl=url_for('home'), delurl=url_for('delTwoFactor'))
|
return render_template('2fa.html', currSecret=currSecret, uri=uri, code=code, homeurl=url_for('home'), delurl=url_for('delTwoFactor'))
|
||||||
|
|
||||||
@app.route('/del2fa')
|
@app.route('/del2fa')
|
||||||
def delTwoFactor():
|
def delTwoFactor():
|
||||||
@@ -152,6 +166,7 @@ def login():
|
|||||||
<form method="POST">
|
<form method="POST">
|
||||||
<label>Username{{ form.username() }}</label>
|
<label>Username{{ form.username() }}</label>
|
||||||
<label>Password{{ form.password() }}</label>
|
<label>Password{{ form.password() }}</label>
|
||||||
|
<label>MFA Code{{ form.mfacode() }}</label>
|
||||||
{{ form.submit() }}
|
{{ form.submit() }}
|
||||||
{{ form.hidden_tag() }}
|
{{ form.hidden_tag() }}
|
||||||
</form>
|
</form>
|
||||||
@@ -159,16 +174,56 @@ def login():
|
|||||||
|
|
||||||
# Instantiate a LDAPLoginForm which has a validator to check if the user
|
# Instantiate a LDAPLoginForm which has a validator to check if the user
|
||||||
# exists in LDAP.
|
# exists in LDAP.
|
||||||
form = LDAPLoginForm()
|
form = LDAPLoginForm0()
|
||||||
|
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
# Successfully logged in, We can now access the saved user object
|
# Successfully logged in, We can now access the saved user object
|
||||||
# via form.user.
|
# via form.user.
|
||||||
login_user(form.user) # Tell flask-login to log them in.
|
login_user(form.user) # Tell flask-login to log them in.
|
||||||
return redirect('/') # Send them home
|
if getMfaSecret(user=current_user.data['sAMAccountName']):
|
||||||
|
if form.mfacode.data == generateOTP(getMfaSecret(user=current_user.data['sAMAccountName'])):
|
||||||
|
return redirect('/') # Send them home
|
||||||
|
else:
|
||||||
|
logout_user()
|
||||||
|
form.mfacode.errors.append("Invalid MFA Code")
|
||||||
|
return redirect('/')
|
||||||
|
elif not getMfaSecret(user=current_user.data['sAMAccountName']):
|
||||||
|
login_user(form.user) # Tell flask-login to log them in.
|
||||||
|
return redirect('/')
|
||||||
return render_template_string(template, form=form)
|
return render_template_string(template, form=form)
|
||||||
|
|
||||||
|
@app.route('/changepw', methods=['GET', 'POST'])
|
||||||
|
def changepw():
|
||||||
|
if not current_user or current_user.is_anonymous:
|
||||||
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
|
form = _forms.ChangePasswordForm(request.form)
|
||||||
|
if request.method == 'POST' and form.validate():
|
||||||
|
new_password = form.newpw.data
|
||||||
|
if updatePassword(cu=current_user.data['sAMAccountName'], newpw=new_password, adinfo=adinfo):
|
||||||
|
print('Password changed successfully!')
|
||||||
|
# flash('Password changed successfully!', 'success')
|
||||||
|
return redirect(url_for('home'))
|
||||||
|
else:
|
||||||
|
print('Failed to change password. Please try again.')
|
||||||
|
# flash('Failed to change password. Please try again.', 'danger')
|
||||||
|
return render_template('changepw.html', form=form)
|
||||||
|
|
||||||
|
@app.route('/vpn', methods=['GET', 'POST'])
|
||||||
|
def vpn():
|
||||||
|
if not current_user or current_user.is_anonymous:
|
||||||
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
|
if request.args.get('dev'):
|
||||||
|
return send_file(genVPN(cu=current_user.data['sAMAccountName'], dev=request.args.get('dev')), as_attachment=True)
|
||||||
|
|
||||||
|
return render_template('vpn.html')
|
||||||
|
|
||||||
|
@app.route('/logout')
|
||||||
|
def logout():
|
||||||
|
logout_user()
|
||||||
|
return redirect('/')
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app.run()
|
app.run(host='0.0.0.0', port=81)
|
||||||
14
ad.py
14
ad.py
@@ -1,8 +1,18 @@
|
|||||||
from ms_active_directory import ADDomain
|
from ms_active_directory import ADDomain
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
|
||||||
|
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG)
|
||||||
|
|
||||||
def updateMfaSecret(user, secret):
|
def updateMfaSecret(user, secret):
|
||||||
domain = ADDomain('corp.bbrunson.com')
|
domain = ADDomain('corp.bbrunson.com')
|
||||||
session = domain.create_session_as_user('administrator@bbrunson.com', 'Mariposa2502$$$$')
|
session = domain.create_session_as_user('administrator@bbrunson.com', 'Mariposa2502$$$$')
|
||||||
|
|
||||||
success = session.overwrite_attribute_for_user(user, 'mfaSecret',
|
success = session.overwrite_attribute_for_user(user, 'mfaSecret',
|
||||||
secret)
|
secret)
|
||||||
|
|
||||||
|
def updatePassword(cu, newpw, adinfo):
|
||||||
|
domain = ADDomain('corp.bbrunson.com')
|
||||||
|
session = domain.create_session_as_user(user=adinfo.get('adbind_user', ''), password=adinfo.get('adbind_pass', ''))
|
||||||
|
return session.reset_password_for_account(account=(session.find_user_by_sam_name(cu)), new_password=newpw)
|
||||||
|
|
||||||
|
# updatePassword(current_user='brandon', newpw='Mariposa2502$$', oldpw='Mariposa2502$')
|
||||||
5
mfa.py
5
mfa.py
@@ -11,4 +11,7 @@ def generateOTP(secret):
|
|||||||
# return totp.verify(code)
|
# return totp.verify(code)
|
||||||
|
|
||||||
def generateSecret():
|
def generateSecret():
|
||||||
return pyotp.random_base32()
|
return pyotp.random_base32()
|
||||||
|
|
||||||
|
def generateProvisioningUri(currSecret, cu):
|
||||||
|
return pyotp.totp.TOTP(currSecret).provisioning_uri(name=cu, issuer_name='Bbrunson Services')
|
||||||
BIN
templates/.DS_Store
vendored
Normal file
BIN
templates/.DS_Store
vendored
Normal file
Binary file not shown.
@@ -8,6 +8,9 @@
|
|||||||
<body>
|
<body>
|
||||||
<h1>{{ currSecret }}</h1>
|
<h1>{{ currSecret }}</h1>
|
||||||
<h1>{{ code }}</h1>
|
<h1>{{ code }}</h1>
|
||||||
|
<a href="{{ uri }}">
|
||||||
|
<button>Add to Authenticator App</button>
|
||||||
|
</a>
|
||||||
<a href="{{ homeurl }}">
|
<a href="{{ homeurl }}">
|
||||||
<button>Back home</button>
|
<button>Back home</button>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
18
templates/changepw.html
Normal file
18
templates/changepw.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 - Change PW</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{{ get_flashed_messages() }}
|
||||||
|
{{ form.errors }}
|
||||||
|
<form method="POST">
|
||||||
|
<label>New Password{{ form.newpw() }}</label>
|
||||||
|
<label>Confirm Password{{ form.conpw() }}</label>
|
||||||
|
{{ form.submit() }}
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -9,11 +9,40 @@
|
|||||||
<h1>Welcome, {{ current_user.data.givenName }}</h1>
|
<h1>Welcome, {{ current_user.data.givenName }}</h1>
|
||||||
<h2>Email: {{ current_user.data.mail }}</h2>
|
<h2>Email: {{ current_user.data.mail }}</h2>
|
||||||
<h2>{{ current_user.dn }}</h2>
|
<h2>{{ current_user.dn }}</h2>
|
||||||
|
MFA status:<br>
|
||||||
<a href="{{ mfaurl }}">
|
<a href="{{ mfaurl }}">
|
||||||
<button>MFA</button>
|
{{ mfastatus }}
|
||||||
</a>
|
</a>
|
||||||
<a href="http://accounts.bbrunson.com">
|
<style>
|
||||||
|
.styled-box {
|
||||||
|
width: 300px;
|
||||||
|
height: 150px;
|
||||||
|
background-color: #3498db;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.styled-box:hover {
|
||||||
|
background-color: #2980b9;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<a href="{{ mfaurl }}">
|
||||||
|
<div class="styled-box">
|
||||||
|
<h2>MFA Status:</h2>
|
||||||
|
<p>{{ mfastatus }}</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a href="{{ changepwurl }}">
|
||||||
<button>Change Password</button>
|
<button>Change Password</button>
|
||||||
</a>
|
</a>
|
||||||
|
<a href="{{ vpnurl }}">
|
||||||
|
<button>VPN</button>
|
||||||
|
</a>
|
||||||
|
<a href="{{ logouturl }}">
|
||||||
|
<button>Logout</button>
|
||||||
|
</a>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
910
templates/people.html
Normal file
910
templates/people.html
Normal file
File diff suppressed because one or more lines are too long
17
templates/vpn.html
Normal file
17
templates/vpn.html
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Accounts - VPN</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<form action="/vpn?dev=ios" method="post">
|
||||||
|
<button type="submit">Download VPN for iOS</button>
|
||||||
|
</form>
|
||||||
|
<form action="/vpn?dev=mac" method="post">
|
||||||
|
<button type="submit">Download VPN for Mac</button>
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
12
vpn.py
Normal file
12
vpn.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
def genVPN(cu, dev):
|
||||||
|
result = subprocess.call(['sh', 'ikev2.sh', '--addclient', cu+'-'+dev])
|
||||||
|
if result == "Error: Invalid client name. Client " + cu+'-'+dev + " already exists.":
|
||||||
|
return getVPN(cu, dev)
|
||||||
|
else:
|
||||||
|
return "profiles/"+cu+'-'+dev+".mobileconfig"
|
||||||
|
|
||||||
|
def getVPN(cu, dev):
|
||||||
|
return "profiles/"+cu+'-'+dev+".mobileconfig"
|
||||||
Reference in New Issue
Block a user