From de829904162705b8f782ec9b93632a8e20b6b811 Mon Sep 17 00:00:00 2001 From: "B. Stack" Date: Tue, 22 Jun 2021 22:45:17 -0400 Subject: add basic auth, but lacks request for auth Endpoint /login/basic/ works now with a POST, but a GET does not make it prompt a browser for username/password yet. --- INTERACT.md | 12 +++++-- session_app.py.publish | 92 ++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 76 insertions(+), 28 deletions(-) diff --git a/INTERACT.md b/INTERACT.md index c36b238..0ead9fe 100644 --- a/INTERACT.md +++ b/INTERACT.md @@ -12,7 +12,7 @@ Try visiting protected page without authorization. $ curl -L http://d2-03a.ipa.example.com:5000/protected -b ~/cookiejar.txt -c ~/cookiejar.txt requires session -Get kerberos ticket and then visit login url. This /login redirects to /login/kerberos by default. +Get kerberos ticket and then visit kerberos login url. $ kinit ${USER} $ klist @@ -23,7 +23,7 @@ Get kerberos ticket and then visit login url. This /login redirects to /login/ke 06/20/21 16:04:10 06/21/21 16:04:07 krbtgt/IPA.EXAMPLE.COM@IPA.EXAMPLE.COM 06/20/21 16:04:15 06/21/21 16:04:07 HTTP/d2-03a.ipa.example.com@IPA.EXAMPLE.COM - $ curl -L http://d2-03a.ipa.example.com:5000/login --negotiate -u ':' -b ~/cookiejar.txt -c ~/cookiejar.txt + $ curl -L http://d2-03a.ipa.example.com:5000/login/kerberos --negotiate -u ':' -b ~/cookiejar.txt -c ~/cookiejar.txt success with kerberos Visit protected page now that we have a session. @@ -49,3 +49,11 @@ Visit protected page now that we have a session. 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 + +Basic auth can be provided as a POST to /login/basic/. + + $ curl -X POST -L http://d2-03a:5000/login/basic/ -b ~/cookiejar.txt -c ~/cookiejar.txt --user 'bgstack15' + Enter host password for user 'bgstack15': + success with ldap + $ curl -X POST -L http://d2-03a:5000/login/basic/ -b ~/cookiejar.txt -c ~/cookiejar.txt --header "Authorization: Basic $( printf '%s' "${username}:${pw}" | base64 )" + success with ldap diff --git a/session_app.py.publish b/session_app.py.publish index 4a806ed..31df674 100755 --- a/session_app.py.publish +++ b/session_app.py.publish @@ -9,11 +9,14 @@ # timeout sessions https://stackoverflow.com/a/11785722/3569534 # future: https://code.tutsplus.com/tutorials/flask-authentication-with-ldap--cms-23101 # better timeout session: https://stackoverflow.com/a/49891626/3569534 +# store "formdata" in session for changing the basic auth to form data for the ldap login https://stackoverflow.com/a/56904875/3569534 # Improve: # move all configs to config file # move all references to references section -# accept a /login/basic endpoint with Authorization: header, use ldap +# make /login/basic actually request http auth if user/pw not provided, or is a GET +# try inspecting flask-httpauth auth.login_required section? # accept a bind credential so we can perform lookups of users who match "uid=%s" under a basedn. +# accept a ldap dns domain name, and a SRV lookup for _tcp._ldap # Run: # FLASK_APP=session_app.py FLASK_DEBUG=1 flask run --host 0.0.0.0 # Dependencies: @@ -109,8 +112,22 @@ def requires_authn_ldap(function): ''' @wraps(function) def decorated(*args, **kwargs): - username = request.form['username'] - pw = request.form['password'] + # formdata is in session if we are coming from login_basic() + form = session.get('formdata', None) + if form: + print(f"DEBUG: requires_authn_ldap form={form}") + session.pop('formdata') + if 'username' in form: + username = form['username'] + if 'password' in form: + pw = form['password'] + else: + # then we are coming from the form with POST data + if 'username' not in request.form or 'password' not in request.form: + return _unauthorized_ldap() + username = request.form['username'] + pw = request.form['password'] + #print(f"DEBUG: requires_authn_ldap with username={username} and pw={pw}") ll = ldap_login(username,pw) if ll: return function(ll.user,*args, **kwargs) @@ -162,20 +179,12 @@ def login(user="None"): # default, show login form return redirect(url_for("login_form")) elif request.method == "POST": - # 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 + if request.authorization: + return redirect(url_for("login_basic"),code=307) + return handle_login_ldap_from_non_ldap(request) def ldap_login(username,password): - print(f"Trying user {username} with pw '{password}'") + #print(f"DEBUG: Trying user {username} with pw '{password}'") user = session_ldap.authenticated_user( app.config['LDAP_URI'], app.config['LDAP_USER_FORMAT'], @@ -217,18 +226,34 @@ def login_ldap(user,groups=[]): resp = login_generic(session,resp,user,groups) return resp -@app.route("/login/form", methods=['GET']) -@app.route("/login/form/", methods=['GET']) +@app.route("/login/form", methods=['POST','GET']) +@app.route("/login/form/", methods=['POST','GET']) def login_form(): - options = { - "ldap": "ldap", - "other": "other" - } - return render_template("login_form.html", - login_url = url_for("login"), - options=options, - kerberos_url = url_for("login_kerberos") - ) + if request.method == "GET": + options = { + "ldap": "ldap", + "other": "other" + } + return render_template("login_form.html", + login_url = url_for("login"), + options=options, + kerberos_url = url_for("login_kerberos") + ) + else: + # assume it is a POST + return redirect(url_for("login_ldap"), code=307) + +def handle_login_ldap_from_non_ldap(request): + # set default logintype for user + logintype = "ldap" + # redirect to whichever option was chosen in the drop-down + if 'logintype' in request.form: + logintype = request.form['logintype'] + 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 @app.route("/logout") @app.route("/logout/") @@ -242,6 +267,21 @@ def logout(): resp.set_cookie('timestamp','',expires=0) return resp +@app.route("/login/basic",methods=['POST']) +@app.route("/login/basic/",methods=['POST']) +def login_basic(): + if not request.authorization: + return Response(f"No username and password provided.",401) + if 'username' not in request.authorization: + return Response(f"No username provided.",401) + if 'password' not in request.authorization: + return Response(f"No password provided.",401) + username = request.authorization.username + pw = request.authorization.password + form={'username':username,'password':pw} + session['formdata'] = form + return redirect(url_for("login_ldap"),code=307) + ## This bumps the session lifetime to two minutes farther out from each web request with this session. #@app.before_request #def make_session_permanent(): -- cgit