From a64e527faca87bb5d23c472e3eaa73474d0805df Mon Sep 17 00:00:00 2001 From: brandon Date: Mon, 20 May 2024 16:10:32 -0700 Subject: [PATCH] active directory authentication, full mfa implementation, vpn generation --- .DS_Store | Bin 0 -> 6148 bytes __pycache__/_forms.cpython-312.pyc | Bin 0 -> 943 bytes __pycache__/ad.cpython-312.pyc | Bin 641 -> 1388 bytes __pycache__/vpn.cpython-312.pyc | Bin 0 -> 836 bytes __pycache__/yamlcon.cpython-312.pyc | Bin 755 -> 755 bytes _forms.py | 8 + accounts.py | 83 ++- ad.py | 14 +- mfa.py | 5 +- templates/.DS_Store | Bin 0 -> 6148 bytes templates/2fa.html | 3 + templates/changepw.html | 18 + templates/home.html | 33 +- templates/people.html | 910 ++++++++++++++++++++++++++++ templates/vpn.html | 17 + vpn.py | 12 + 16 files changed, 1084 insertions(+), 19 deletions(-) create mode 100644 .DS_Store create mode 100644 __pycache__/_forms.cpython-312.pyc create mode 100644 __pycache__/vpn.cpython-312.pyc create mode 100644 _forms.py create mode 100644 templates/.DS_Store create mode 100644 templates/changepw.html create mode 100644 templates/people.html create mode 100644 templates/vpn.html create mode 100644 vpn.py diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..3f007a00b8b123b8e2191abe66f8d7fa092b0d7e GIT binary patch literal 6148 zcmeHKO^?$s5FK~hZn71qNQ<-=q)1$=Qg-}P#$P1;1YYu44C}0%$w-w;ETctKlD4-<2e#I>;>+3(2sU!}ze(;@8M3Uw- zE^s~k&jDL3JbGB_PIO`%UKvPfeusT6~Y7ULYyCkI5mvz zmCds9n=;sS%S z`~$;V#xuR6eDh{u5GS3^cTw4^*6RD#zSXea4KCzzkhsYr?z!_feDzYw(A&0M?^Q5f z4x5M1WR$o;G@dI$;E&<*_H_{Xa@muM$d46wklwIt%N{n5R;%NO_gl`%qt3eJtR5eC zTF$+b2kW(M-MoG0$=UEjIE~~dWC0V{0hL|TxCPe5E`p literal 0 HcmV?d00001 diff --git a/__pycache__/_forms.cpython-312.pyc b/__pycache__/_forms.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c03e4e2aa13e4b20301e1cc8be6bcd21c3f90728 GIT binary patch literal 943 zcmZ`%O=}ZD7=CB6`Dn9SL5eL{VS9|C@#M)Av|lIN;s=GLxS3|AX-alCo!K=ccu?q} z|DbxZS4n?>_!qo1^bi>l1VQjtNWJvr%qH7Lq;r^go_F47=ACEWFP1e9YCNd+*=Gds zQrEtDR2M`97Msv7+7j2*&E5HNX6qfj06?jvHsPA<|qRA$iJnqZ(&a zW*4>Yc_7n1Yc`rZt5foW>td12i1_Z0(APGc&lO(?#zk?H6Q2ftaUrNg9`i#{Tx*5f zfp4$zpvs9SimN`eA5|(r>n6vxKJ#)5e&K}ts1B4EJ_E3}@SDZ2 zqeq^Y_f^EQ_t$%t-L>q)=@aYr+a*4g)KW-u-9i!*%5~pMLx1C-)*Cu0&YV{0Joj*5 m`W(NhsBRab!D$L1^bIDy!>u#JKzH6veVBPa^9K}+#eV^`9N_T) literal 0 HcmV?d00001 diff --git a/__pycache__/ad.cpython-312.pyc b/__pycache__/ad.cpython-312.pyc index ec1aa82a9c9ec830f2eca09eafd4a94e669d945a..00627e42ef2e603d9c7d54f63f9a631f19367e8b 100644 GIT binary patch literal 1388 zcmZ`(&2Jk;6rcU@N9ttz5t2jTkOPMB7%rfL!q%_7}_>#=8l=ZtsTom)U4)*g`+pMsww^` zqAkcBe>S4MW9!K5M*-jd7VYFn26Vn@y4ip9f@`S!%IYc)alkfQQ)Oi&C=OMoM)zsW z9~7q$8PISX)FYrCqR2?2ponoaun)|ZS=Bk0gqY;Kkprt`RW;TnX+eW6<7iNQU^kJQ z**LrIb5UO?mqpAZXA3?LznZ}$3|Ih-;)sj)pIN)Mjzv)C5|`e(ReBXfjfH#oF7*YC zTuW$hH4P4qs3)nE0cReTUM#6lukd{;8X|xt97Q51$6)Pok*>NKWv5wLnS!Y-NxzxO z4Pd~CeIFpDKHIF8v*qFz0Lx-oU`9AAu5v#PDT`!rvl%_$Y<5%diol^Pe#+?VitqE7 zMMX>&>P>Yut`q2Teb~LY4zM|RnRYu9uN{7L>b%r-=Gx9&^7_ZgC!cqm z-NUt0XR7Oz+D_@@&BrgF-g>)d8CRFSS?fV9@-I$ELu$YGE2tMtRZr|<4PTWIxM8__Lz-^%+=UT>bc8GBoxw#FYK~w>ga==J`v?I5U zrF2bE&?$NCG`JwZ`yB3neKpQ#!#iKdq?8f50cBNaq-F$_G&)a6`7tb=+8b`@m7AW# zp~rAYfjOohs|?`Q8!C%Qz<^W}fG8YLBf5xddKafZ_sv0TVitlrS$+ZP`C~ts{$=Lp znaAtDl{#;1B->th+i!3C3HdUSac6r!appQs11M3r(Jjok3-c$9?&7`n;=N?+erNHs z0C7V(e%2)>e2b}E% z75(cVJdRXm^}{<`YbjsT?jNBPHa*^1{@sw1xJKU-H(@LN+mN?G{c9P9@h7_Z1UXO8 rmA}yBVXliN+i3E;+dnLSzx?C%$e7NM!jQt4!;s4u#mLBz!qmbL z#Z<|x$?_7UNR#mvhog&2er{rB-o(?5j8&U`8Mzo)`G8W)lVg}POCIovUgtBq$Y->{ zWQX((1GCS}jQkdj?w>)L{WO_x3Fj8aCnlF=p0G9?Jj!7Pom2U99iE$ATziV|`uo1JMAyV(shn_xoJ z9=uc!_8>IJT&0)x)}wbXS-q5P4}vFeVs1J4CP|@!59XWqGw+*WzV|g2iy$!d{eZa` zp`S9iG-cup2snp`B8n|k9PhY@sqzjL6{-TNEu7MR$>TZmvP(umSSqZ+CxAmFZy4Ll ztEVP83XB~@)i%T(EU~N}O*&pH9ffKgB(qmf(16VBE+NFH+yJ;E>p=g})9RNCR{E^B7eqB6vH+sVhAJN!O*VmA$|N{MMzne7tWsIfHJ7zgbcVpyCR zv_;sfs&GYmc&*NzovOuzK8&SnP6=Gov3IKFl3E^v|VOf5#%-k3G9MlxE z;j5H2{UB$eVYx#77$Ibs)t?K-g6%`Av6uAX zcY2N&U+ITe25<}dX9lDWqZwEL2c`&5)KH`XW1Yu}f*M+m%aP$yNge|Dj8Y1Z%4iBe zNG?M7v`omk9f2&1+W>p$EPCUB@2_{0UUVMb7G3P=UUa#yE%O0F$eHl#TcKX$T>><(E@mNFpY6$)b690q{^ delta 20 acmey&`k9scG%qg~0}w3Dxwet}1rq>6HU@YA diff --git a/_forms.py b/_forms.py new file mode 100644 index 0000000..03aaa65 --- /dev/null +++ b/_forms.py @@ -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') \ No newline at end of file diff --git a/accounts.py b/accounts.py index fc1a84d..daf9947 100644 --- a/accounts.py +++ b/accounts.py @@ -1,12 +1,17 @@ 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_login import LoginManager, login_user, UserMixin, current_user, logout_user +from flask import render_template_string, redirect, render_template, request, send_file from flask_ldap3_login.forms import LDAPLoginForm -from mfa import generateOTP, generateSecret +from mfa import generateOTP, generateSecret, generateProvisioningUri # from ad import updateMfaSecret from db import setupMfaSecret, updateMfaSecret, getMfaSecret, removeMfa 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.config['SECRET_KEY'] = 'secret' @@ -64,7 +69,9 @@ class User(UserMixin): def get_id(self): return self.dn - + +class LDAPLoginForm0(LDAPLoginForm): + mfacode = StringField('MFA') # Declare a User Loader for Flask-Login. # Simply returns the User if it exists in our 'database', otherwise @@ -93,6 +100,11 @@ def home(): # Redirect users who are not logged in. if not current_user or current_user.is_anonymous: 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. # template = """ @@ -101,12 +113,13 @@ def home(): #

Email: {{ current_user.data.mail }}

#

{{ current_user.dn }}

# """ - print(current_user) - print(current_user.data) - print(current_user.data['objectSid']) + # 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')) + 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') def two_factor(): @@ -121,10 +134,11 @@ def two_factor(): setupMfaSecret(user=current_user.data['sAMAccountName'], secret=generateSecret()) # currSecret = current_user.data['mfaSecret'] currSecret = getMfaSecret(user=current_user.data['sAMAccountName']) + uri = generateProvisioningUri(currSecret=currSecret, cu=current_user.data['sAMAccountName']) code = '' 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') def delTwoFactor(): @@ -152,6 +166,7 @@ def login():
+ {{ form.submit() }} {{ form.hidden_tag() }}
@@ -159,16 +174,56 @@ def login(): # Instantiate a LDAPLoginForm which has a validator to check if the user # exists in LDAP. - form = LDAPLoginForm() + form = LDAPLoginForm0() 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 - + login_user(form.user) # Tell flask-login to log them in. + 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) +@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__': - app.run() \ No newline at end of file + app.run(host='0.0.0.0', port=81) \ No newline at end of file diff --git a/ad.py b/ad.py index 991610d..b262bd3 100644 --- a/ad.py +++ b/ad.py @@ -1,8 +1,18 @@ from ms_active_directory import ADDomain +import logging +import sys + +logging.basicConfig(stream=sys.stdout, level=logging.DEBUG) 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 + 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$') \ No newline at end of file diff --git a/mfa.py b/mfa.py index 9d03d95..7ae5893 100644 --- a/mfa.py +++ b/mfa.py @@ -11,4 +11,7 @@ def generateOTP(secret): # return totp.verify(code) def generateSecret(): - return pyotp.random_base32() \ No newline at end of file + return pyotp.random_base32() + +def generateProvisioningUri(currSecret, cu): + return pyotp.totp.TOTP(currSecret).provisioning_uri(name=cu, issuer_name='Bbrunson Services') \ No newline at end of file diff --git a/templates/.DS_Store b/templates/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..6e0e9bec41d79e9adea4865d08e9f2f6f637758c GIT binary patch literal 6148 zcmeHKOHKnZ41IF0|5}Y7ZWx<*YK>3KpRKo)HxlxZ;oSg5GNcqp|4M7tto*uusJe95iccrsYHt* zUe56nc{Q*(dO0MT4~dmOnisLvIe)QqNOeq`3?u`)3~295nd$w1#lOsGlW!?$l7VF4 zzcL_`<;`-z-NoJdZJ&DA7V0fkMe~|E6#6@l0RGT( literal 0 HcmV?d00001 diff --git a/templates/2fa.html b/templates/2fa.html index d4454da..5f39ea7 100644 --- a/templates/2fa.html +++ b/templates/2fa.html @@ -8,6 +8,9 @@

{{ currSecret }}

{{ code }}

+ + + diff --git a/templates/changepw.html b/templates/changepw.html new file mode 100644 index 0000000..fcf91ea --- /dev/null +++ b/templates/changepw.html @@ -0,0 +1,18 @@ + + + + + + Accounts - Change PW + + + {{ get_flashed_messages() }} + {{ form.errors }} +
+ + + {{ form.submit() }} +
+ + + \ No newline at end of file diff --git a/templates/home.html b/templates/home.html index 000b2b0..ab2929d 100644 --- a/templates/home.html +++ b/templates/home.html @@ -9,11 +9,40 @@

Welcome, {{ current_user.data.givenName }}

Email: {{ current_user.data.mail }}

{{ current_user.dn }}

+ MFA status:
- + {{ mfastatus }} - + + +
+

MFA Status:

+

{{ mfastatus }}

+
+
+ + + + + + + + \ No newline at end of file diff --git a/templates/people.html b/templates/people.html new file mode 100644 index 0000000..5391973 --- /dev/null +++ b/templates/people.html @@ -0,0 +1,910 @@ + + + + + + + + + + + + + + + + + + + + + People + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + + + +
+
+ + + + +
+ +
+
+ + +
+

Popular Searches

+ +
+
+
+
+ + + + +
+ + +
+

Home

+ + + +
+ + +
+ + +
+ +
+
+
+
Clickable image title
+
+
+
+

The Retail Story

+

Discover new team stories of Retail’s Credo and culture in action.

+ +

Discover more

A dramatic turn of events

+

The Singapore RCC team reenact their mission to help a customer in need.

+
+
+
+
+

Spotlight

+

Black History Month (U.S. and Canada)

+

Black History Month honors the central role of Black people in U.S. and Canadian history.

+

Long-term care insurance

+

Enroll by March 1 to purchase private long-term care insurance through Trustmark.

+

Get help when you need it

+

Apple’s Employee Assistance Program (EAP) supports you and your family in a number of ways.

+

Keep fueling your passion for learning

+

Get access to free on-demand, online learning through Coursera. Or use the Free Tuition Program at select schools.

+

Stay safe with the Global Security app

+

Get fast access to emergency support, Apple Emergency Alerts, and safety guidance.

+

Reminders

+
+
+ Throughout February
+
+

Financial education: Tax Planning Strategies

+
+
+ Available February 21 and after
+
+

Cleo parent webinar: Setting Boundaries as a Parent and Caregiver

+
+
+ Time Away
+
+

Check out the public holiday schedule

+

Find contact information for Apple benefits providers

+
+
+ This week
+
+

Find new, inspiring, and helpful resources in Hello Apple

+
+
+
+
+

Set yourself up for success

+
Starting a new job at Apple can be exciting, daunting, and everything in between.
+
+
+
+

How we help our people

+

Apple provides benefits, programs, and tools to help you feel supported and make your life easier.

+

We belong. Together.

+

At Apple, we continue to build a culture where everybody belongs.          

+

Reporting a concern

+

We encourage any employee with a concern to raise it in the way they feel most comfortable, internally or externally.

+

Your Apple benefits

+

Explore how Apple benefits provide care and support for you and your loved ones.

+
+
+
+ +
+ +
+
+ +
+ + +
+ + +
+
+
+ + +
+ + + + diff --git a/templates/vpn.html b/templates/vpn.html new file mode 100644 index 0000000..9a253f8 --- /dev/null +++ b/templates/vpn.html @@ -0,0 +1,17 @@ + + + + + + Accounts - VPN + + +
+ +
+
+ +
+ + + \ No newline at end of file diff --git a/vpn.py b/vpn.py new file mode 100644 index 0000000..0d38589 --- /dev/null +++ b/vpn.py @@ -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" \ No newline at end of file