summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorB. Stack <bgstack15@gmail.com>2021-06-21 16:52:56 -0400
committerB. Stack <bgstack15@gmail.com>2021-06-21 16:52:56 -0400
commit1c5123a2b47006e59739959ab67b51129d39a761 (patch)
tree504c05af2c6a305e99c88c5a2f9537c9727950a4
parentWIP: improve login form (diff)
downloadsession_app-1c5123a2b47006e59739959ab67b51129d39a761.tar.gz
session_app-1c5123a2b47006e59739959ab67b51129d39a761.tar.bz2
session_app-1c5123a2b47006e59739959ab67b51129d39a761.zip
add ldap support
-rw-r--r--INTERACT.md4
-rwxr-xr-xsession_app.py.publish130
-rw-r--r--session_ldap.py30
-rw-r--r--templates/login_form.html7
4 files changed, 116 insertions, 55 deletions
diff --git a/INTERACT.md b/INTERACT.md
index b3c3914..c36b238 100644
--- a/INTERACT.md
+++ b/INTERACT.md
@@ -46,4 +46,6 @@ Visit protected page now that we have a session.
Logged in through: kerberos
</html>
-2021-06-20 ldap basic auth, and a login form are still pending.
+For submitting to the form, pass in form data using fields `username`, `password`, and optionally `logintype` which can be defined within the application. An included option is `ldap`. Kerberos auth through the form is not supported.
+
+ curl -L -X POST http://d2-03a:5000/login/ --data 'username=bgstack15&password=qwerty' -b ~/cookiejar.txt -c ~/cookiejar.txt
diff --git a/session_app.py.publish b/session_app.py.publish
index 520f676..4a806ed 100755
--- a/session_app.py.publish
+++ b/session_app.py.publish
@@ -10,25 +10,23 @@
# future: https://code.tutsplus.com/tutorials/flask-authentication-with-ldap--cms-23101
# better timeout session: https://stackoverflow.com/a/49891626/3569534
# Improve:
-# purge sessions after 15 minutes?
+# move all configs to config file
+# move all references to references section
+# accept a /login/basic endpoint with Authorization: header, use ldap
+# accept a bind credential so we can perform lookups of users who match "uid=%s" under a basedn.
# Run:
# FLASK_APP=session_app.py FLASK_DEBUG=1 flask run --host 0.0.0.0
# Dependencies:
# apt-get install python3-flask
# pip3 install Flask-kerberos kerberos
-from flask import Flask, Response, redirect, url_for, render_template, request
-
+from flask import Flask, Response, redirect, url_for, render_template, request, _request_ctx_stack as stack, make_response, session
from flask_kerberos import init_kerberos, requires_authentication, _unauthorized, _forbidden, _gssapi_authenticate
-from flask import _request_ctx_stack as stack, make_response, session
-#from flask.ext.login import LoginManager
import kerberos
from functools import wraps
-from socket import gethostname
import binascii, datetime
-
-from functools import wraps
import os
+import session_ldap
DEBUG=True
app = Flask(__name__)
@@ -37,10 +35,12 @@ app.debug=True
secret_key_value = os.urandom(24)
secret_key_value_hex_encoded = binascii.hexlify(secret_key_value)
app.config['SECRET_KEY'] = secret_key_value_hex_encoded
-#app.config['PERMANENT_SESSION_LIFETIME'] = datetime.timedelta(days=7)
-#session.permanent = True
-minutes = 2
-app.permanent_session_lifetime=datetime.timedelta(minutes=minutes)
+app.config['LDAP_URI'] = "ldaps://dns1.ipa.internal.com:636"
+app.config['LDAP_USER_BASEDN'] = "cn=users,cn=accounts,dc=ipa,dc=internal,dc=com"
+app.config['LDAP_GROUP_BASEDN'] = "cn=groups,cn=accounts,dc=ipa,dc=internal,dc=com"
+app.config['LDAP_USER_FORMAT'] = "uid=%s,cn=users,cn=accounts,dc=ipa,dc=internal,dc=com"
+app.config['minutes'] = 2
+app.permanent_session_lifetime=datetime.timedelta(minutes=app.config['minutes'])
def requires_session(function):
'''
@@ -97,6 +97,27 @@ def requires_authn_kerberos(function):
return _unauthorized_kerberos()
return decorated
+def requires_authn_ldap(function):
+ '''
+ Require that the wrapped view function only be called by users
+ authenticated with ldap. The view function will have the authenticated
+ users principal passed to it as its first argument.
+ :param function: flask view function
+ :type function: function
+ :returns: decorated function
+ :rtype: function
+ '''
+ @wraps(function)
+ def decorated(*args, **kwargs):
+ username = request.form['username']
+ pw = request.form['password']
+ ll = ldap_login(username,pw)
+ if ll:
+ return function(ll.user,*args, **kwargs)
+ else:
+ return _unauthorized_ldap()
+ return decorated
+
def _unauthorized_kerberos():
'''
Indicate that authentication is required
@@ -104,20 +125,13 @@ def _unauthorized_kerberos():
# from https://billstclair.com/html-redirect2.html
return Response(f'<meta http-equiv="Refresh" content="4; url={url_for("login_ldap")}">Unauthorized! No kerberos auth provided. Trying <a href="{url_for("login_ldap")}">ldap</a> automatically in a moment.', 401, {'WWW-Authenticate': 'Negotiate'})
+def _unauthorized_ldap():
+ return Response(f'<meta http-equiv="Refresh" content="4; url={url_for("login")}">Unauthorized! Invalid ldap credentials... returning to login form', 401)
+
@app.route("/")
def index():
return render_template('index.html')
-@app.route("/open/")
-def open():
- header = request.headers.get("Authorization")
- if header:
- print("Header!")
- token = ''.join(header.split()[1:])
- print("token",token)
- print("something")
- return "<html><body>here</body></html>", 200
-
@app.route("/protected/")
@requires_session
def protected_page():
@@ -131,11 +145,11 @@ def protected_page_real():
return render_template('view.html', c_user = c_user, s_user=s_user, cookie=cookie)
@app.route("/login/new")
+@app.route("/login/new/")
def login_new():
return redirect(url_for("login", new=""))
@app.route("/login/", methods=['POST','GET'])
-#@requires_authn_kerberos
def login(user="None"):
if request.method == "GET":
if 'user' in session and request.cookies.get('user') == session['user'] and (not 'new' in request.args):
@@ -148,31 +162,44 @@ def login(user="None"):
# default, show login form
return redirect(url_for("login_form"))
elif request.method == "POST":
- # so far only the login form sends a POST to this endpoint.
- username=request.form['username']
- pw=request.form['password']
- #pw="******"
- args=""
- for i in request.args:
- args += str(i)
- #resp = Response(f"Login functionality still in progress. <br/>Args: {args}<br/>data: {request.data}</br>query_string: {request.query_string}<br/>values: {request.values}",200)
- ldap_result = ldap_login(username,pw)
- resp = Response(f"Login functionality still in progress. <br/>username: {username}<br/>password: {pw}<br/>form: {request.form}<br/>ldap result:{ldap_result}",200)
- return resp
-
+ # redirect to whichever option was chosen in the drop-down
+ if 'logintype' in request.form:
+ logintype = request.form['logintype']
+ else:
+ # choose default logintype for user
+ logintype = "ldap"
+ if "ldap" == logintype:
+ # preserve POST with code 307 https://stackoverflow.com/a/15480983/3569534
+ return redirect(url_for("login_ldap"), code=307)
+ else:
+ return f"Authentication method {logintype} not supported yet.",400
+
def ldap_login(username,password):
- response = f"Trying user {username} with pw '{password}'"
- print(response)
- return response
-
+ print(f"Trying user {username} with pw '{password}'")
+ user = session_ldap.authenticated_user(
+ app.config['LDAP_URI'],
+ app.config['LDAP_USER_FORMAT'],
+ username,
+ password
+ )
+ if user:
+ return user
+ else:
+ return False
+ return False
@app.route("/login/kerberos")
+@app.route("/login/kerberos/")
@requires_authn_kerberos
def login_kerberos(user):
resp = Response(f'<meta http-equiv="Refresh" content="1; url={url_for("protected_page")}">success with kerberos')
#resp.headers['login'] = "from-kerberos"
- resp.set_cookie('user',user)
resp.set_cookie('type',"kerberos")
+ resp = login_generic(session,resp,user,None)
+ return resp
+
+def login_generic(session,resp,user,groups=[]):
+ resp.set_cookie('user',user)
end_time = datetime.datetime.now(datetime.timezone.utc) + app.permanent_session_lifetime
end_time_str = datetime.datetime.strftime(end_time,"%FT%TZ")
resp.set_cookie('timestamp',end_time_str)
@@ -181,27 +208,30 @@ def login_kerberos(user):
session['end_time'] = end_time_str
return resp
-# WORKHERE: ldap auth
-# WIP 2021-06-18 17:42; make this unauthenticated GET send to a form.
@app.route("/login/ldap", methods=['POST','GET'])
-#@app.route("/login/ldap/<user>")
-def login_ldap(user = "none"):
- resp = Response(f"success, from user {user}")
- resp.headers['login'] = "from-ldap"
- resp.set_cookie('user',user)
+@app.route("/login/ldap/", methods=['POST','GET'])
+@requires_authn_ldap
+def login_ldap(user,groups=[]):
+ resp = Response(f'<meta http-equiv="Refresh" content="1; url={url_for("protected_page")}">success with ldap')
resp.set_cookie('type',"ldap")
- session['user']=user
- resp.set_cookie('timestamp',app.permanent_session_lifetime)
+ resp = login_generic(session,resp,user,groups)
return resp
+@app.route("/login/form", methods=['GET'])
@app.route("/login/form/", methods=['GET'])
def login_form():
options = {
"ldap": "ldap",
+ "other": "other"
}
- return render_template("login_form.html",login_url=url_for("login"),options=options)
+ return render_template("login_form.html",
+ login_url = url_for("login"),
+ options=options,
+ kerberos_url = url_for("login_kerberos")
+ )
@app.route("/logout")
+@app.route("/logout/")
def logout():
resp = Response(f"logged out")
# Doing anything with session here leaves a cookie.
diff --git a/session_ldap.py b/session_ldap.py
new file mode 100644
index 0000000..b478ef5
--- /dev/null
+++ b/session_ldap.py
@@ -0,0 +1,30 @@
+# python3 library
+# Startdate: 2021-06-21
+# Dependencies:
+# req-devuan: python3-ldap3
+
+# reference: https://github.com/ArtemAngelchev/flask-basicauth-ldap/blob/master/flask_basicauth_ldap.py
+
+import ldap3
+from ldap3.core.exceptions import LDAPBindError, LDAPPasswordIsMandatoryError
+
+def authenticated_user(server_uri, user_format, username, password):
+ user = user_format.replace("%s",username)
+ print(f"server_uri: {server_uri}")
+ print(f"username: {username}")
+ print(f"user_format: {user_format}")
+ print(f"user: {user}")
+ try:
+ server = ldap3.Server(server_uri)
+ conn = ldap3.Connection(server, auto_bind=True, user=user, password=password)
+ return conn
+ except LDAPBindError as e:
+ if 'invalidCredentials' in str(e):
+ print("Invalid credentials.")
+ return False
+ else:
+ raise e
+ #except (LDAPPasswordIsMandatoryError, LDAPBindError):
+ # print("Either an ldap password is required, or we had another bind error.")
+ # return False
+ return False
diff --git a/templates/login_form.html b/templates/login_form.html
index 1b42e60..254d45c 100644
--- a/templates/login_form.html
+++ b/templates/login_form.html
@@ -5,15 +5,14 @@
<body>
<center>
<h1>Login Form</h1>
-<form action="/submit" method="post">
+<a href="{{ kerberos_url }}">Use kerberos</a>
+<form action="{{ login_url }}" method="post">
{% if options %}Login type <select name="logintype">
{% for option in options %}<option value="{{ option }}">{{ options[option] }}</option>{% endfor %}
-{#<option value="ldap">ldap</option>
-<option value="kerberos">kerberos</option>#}
</select>{% endif %}
<p>Username <input type="text" value="" name="username" required/></p>
<p>Password <input type="password" name="password" required/></p>
-<p><input accesskey="s" type="submit" formaction="{{ login_url }}" value="Submit"/></p>
+<p><input accesskey="s" type="submit" value="Submit"/></p>
</form>
</center>
</body>
bgstack15