1
votes

I am new in Python Flask and I am trying to design my personal blog. Since it is only for private use, I would like to put the "add" page for post as protected area with a username and a password just for to add and manage my posts. In order not to show the login credentials in the source code, I decided to store in a .yaml file and retrieve them ({"Franz":{"pw":"Biberkopf"}}) through the function login.

But when I put "Franz" as username and "Biberkopf" as password, I get this error:

werkzeug.exceptions.BadRequestKeyError

werkzeug.exceptions.BadRequestKeyError: 400 Bad Request: The browser (or proxy) sent a request that this server could not understand.
KeyError: 'pw'

Traceback (most recent call last)

File "C:\Users\franc\AppData\Local\Programs\Python\Python38-32\Lib\site-packages\flask\app.py", line 2464, in call

return self.wsgi_app(environ, start_response)

File "C:\Users\franc\AppData\Local\Programs\Python\Python38-32\Lib\site-packages\flask\app.py", line 2450, in wsgi_app

response = self.handle_exception(e)

File "C:\Users\franc\AppData\Local\Programs\Python\Python38-32\Lib\site-packages\flask\app.py", line 1867, in handle_exception

reraise(exc_type, exc_value, tb)

File "C:\Users\franc\AppData\Local\Programs\Python\Python38-32\Lib\site-packages\flask_compat.py", line 39, in reraise

raise value

File "C:\Users\franc\AppData\Local\Programs\Python\Python38-32\Lib\site-packages\flask\app.py", line 2447, in wsgi_app

response = self.full_dispatch_request()

File "C:\Users\franc\AppData\Local\Programs\Python\Python38-32\Lib\site-packages\flask\app.py", line 1952, in full_dispatch_request

rv = self.handle_user_exception(e)

File "C:\Users\franc\AppData\Local\Programs\Python\Python38-32\Lib\site-packages\flask\app.py", line 1821, in handle_user_exception

reraise(exc_type, exc_value, tb)

File "C:\Users\franc\AppData\Local\Programs\Python\Python38-32\Lib\site-packages\flask_compat.py", line 39, in reraise

raise value

File "C:\Users\franc\AppData\Local\Programs\Python\Python38-32\Lib\site-packages\flask\app.py", line 1950, in full_dispatch_request

rv = self.dispatch_request()

File "C:\Users\franc\AppData\Local\Programs\Python\Python38-32\Lib\site-packages\flask\app.py", line 1936, in dispatch_request

return self.view_functionsrule.endpoint

File "C:\Users\franc\OneDrive\Desktop\Blog_Project\blog_prova.py", line 102, in login

user.id = username
flask_login.login_user(user)
return redirect(url_for("add"))
else:
error = "Leider haben Sie die falschen Zugangsdaten."
return render_template("login.html", error=error)
return render_template("login.html")

Code:

@app.route('/add')

@login_required

File "C:\Users\franc\AppData\Local\Programs\Python\Python38-32\Lib\site-packages\flask\templating.py", line 136, in render_template

ctx.app.update_template_context(context)

File "C:\Users\franc\AppData\Local\Programs\Python\Python38-32\Lib\site-packages\flask\app.py", line 838, in update_template_context

context.update(func())

File "C:\Users\franc\AppData\Local\Programs\Python\Python38-32\Lib\site-packages\flask_login\utils.py", line 379, in _user_context_processor

return dict(current_user=_get_user())

File "C:\Users\franc\AppData\Local\Programs\Python\Python38-32\Lib\site-packages\flask_login\utils.py", line 346, in _get_user

current_app.login_manager._load_user()

File "C:\Users\franc\AppData\Local\Programs\Python\Python38-32\Lib\site-packages\flask_login\login_manager.py", line 331, in _load_user

user = self._load_user_from_request(request)

File "C:\Users\franc\AppData\Local\Programs\Python\Python38-32\Lib\site-packages\flask_login\login_manager.py", line 390, in _load_user_from_request

user = self._request_callback(request)

File "C:\Users\franc\OneDrive\Desktop\Blog_Project\blog_prova.py", line 85, in request_loader

    username = request.form.get("username")  # comunica col template <input ...name = "username"> Username </input>

    if username not in users:

        return

    user = User()

    user.id = username

    user.is_authenticated = request.form["pw"] == users[username]["pw"]

 

    return user

 

 

@app.route('/login', methods=["POST", "GET"])

File "C:\Users\franc\AppData\Local\Programs\Python\Python38-32\Lib\site-packages\werkzeug\datastructures.py", line 442, in __getitem__

raise exceptions.BadRequestKeyError(key)

werkzeug.exceptions.BadRequestKeyError: 400 Bad Request: The browser (or proxy) sent a request that this server could not understand.
KeyError: 'pw'

The debugger caught an exception in your WSGI application. You can now look at the traceback which led to the error.

To switch between the interactive traceback and the plaintext one, you can click on the "Traceback" headline. From the text traceback you can also create a paste of it. For code execution mouse-over the frame you want to debug and click on the console icon on the right side.

You can execute arbitrary Python code in the stack frames and there are some extra helpers available for introspection:

dump() shows all variables in the frame
dump(obj) dumps all that's known about the object

Edit: If I run the Pycharm debugger, it seems that there are some problems with the method is_authenticated (Property 'is_authenticated' cannot be set). I don´t know if it is somehow inherent.

This is my sample code: 


```
from datetime import datetime
import flask_login
import yaml
from flask import Flask, render_template, request, redirect, url_for 
from flask_login import LoginManager, UserMixin, login_required, logout_user
from flask_sqlalchemy import SQLAlchemy


app = Flask(__name__)
app.config['TESTING'] = False
app.config['SECRET_KEY'] = 'chiavesegreta'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////Users/Desktop/Blog_Project/blog2.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)

login_manager = LoginManager()
login_manager.init_app(app)


# {"Franz":{"pw":"Biberkopf"}}
with open(r'C://Users//Desktop//application.yaml') as file:
    users = yaml.full_load(file)


class User(UserMixin):
    pass


class Blogpost2(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(50))
    subtitle = db.Column(db.String(50))
    author = db.Column(db.String(20))
    date_posted = db.Column(db.DateTime)
    content = db.Column(db.Text)

db.create_all()


@app.route('/')
def index():
    posts = Blogpost2.query.order_by(Blogpost2.date_posted.desc()).all()

    return render_template('index.html', posts=posts)


@app.route('/about')
def about():
    return render_template('about.html')


@app.route('/post/<int:post_id>')
def post(post_id):
    post = Blogpost2.query.filter_by(id=post_id).one() 

    date_posted = post.date_posted.strftime('%d %B, %Y')

    return render_template('post.html', post=post, date_posted=date_posted)


@login_manager.user_loader
def user_loader(username):
    if username not in users:
        return

    user = User()
    user.id = username
    return user


@login_manager.request_loader
def request_loader(request):
    username = request.form.get("username")  
    if username not in users:
        return
    user = User()
    user.id = username
    user.is_authenticated = request.form["pw"] == users[username]["pw"]

    return user


@app.route('/login', methods=["POST", "GET"])
def login():
    error = None
    if request.method == 'POST':
        username = request.form.get('username')
        if request.form.get('pw') == users[username]["pw"]:
            user = User()
            user.id = username
            flask_login.login_user(user)
            return redirect(url_for("add"))
        else:
            error = "Leider haben Sie die falschen Zugangsdaten."
            return render_template("login.html", error=error)
    return render_template("login.html")


@app.route('/add')
@login_required
def add():
    return render_template('add.html')


@app.route('/logout')
def logout():
    logout_user()
    return "Logged out !"



@app.route('/contact')
def contact():
    return render_template('contact.html')


@app.route('/prova')
def prova():
    return render_template('prova.html')


@app.route('/addpost', methods=['POST'])
def addpost():
    title = request.form['title']
    subtitle = request.form['subtitle']
    author = request.form["author"]
    content = request.form['content']

    post = Blogpost2(title=title, subtitle=subtitle, author=author, content=content, date_posted=datetime.now())

    db.session.add(post)
    db.session.commit()

    return redirect(url_for('index'))


if __name__ == "__main__":
    app.run(debug=True)


The idea would be that, if someone tries to log in without the right username and password, an error message would be displayed. With the right credentials you can have access to the "add" page. Here my login.html page:

<!DOCTYPE html>
<html lang="en">

<head>

  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <meta name="description" content="">
  <meta name="author" content="">

  <title>Clean Blog - Start Bootstrap Theme</title>

  <!-- Bootstrap core CSS -->
  <!-- <link href="vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet"> -->
  <link href= "{{ url_for('static', filename='bootstrap.min.css')}}" rel="stylesheet">

  <!-- Custom fonts for this template -->
  <!--  <link href="vendor/fontawesome-free/css/all.min.css" rel="stylesheet" type="text/css">                              -->
  <link href="{{url_for('static', filename = 'fontawesome.min.css')}}" rel="stylesheet" type="text/css">
  <link href='https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic' rel='stylesheet' type='text/css'>
  <link href='https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800' rel='stylesheet' type='text/css'>

  <!-- Custom styles for this template -->
  <link href="{{url_for('static', filename = 'clean-blog.min.css')}}" rel="stylesheet">

</head>

<body>

   <!-- Navigation -->
  <nav class="navbar navbar-expand-lg navbar-light fixed-top" id="mainNav">
    <div class="container">
      <a class="navbar-brand" href= "{{ url_for('index') }}">Start Bootstrap</a>
      <button class="navbar-toggler navbar-toggler-right" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
        Menu
        <i class="fas fa-bars"></i>
      </button>
      <div class="collapse navbar-collapse" id="navbarResponsive">
        <ul class="navbar-nav ml-auto">
          <li class="nav-item">
            <a class="nav-link" href="{{ url_for('index')}}">Home</a>
          </li>
          <li class="nav-item">
            <a class="nav-link" href="{{ url_for('about')}}">About</a>
          </li>
          <li class="nav-item">
            <a class="nav-link" href="{{ url_for('contact')}}">Contact</a>
          </li>
          <li class="nav-item">
            <a class="nav-link" href="{{ url_for('prova')}}">Prova</a>
          </li>
        </ul>
      </div>
    </div>
  </nav>

  <!-- Page Header -->
  <header class="masthead" style="background-image: url('img/contact-bg.jpg')">
    <div class="overlay"></div>
    <div class="container">
      <div class="row">
        <div class="col-lg-8 col-md-10 mx-auto">
          <div class="page-heading">
            <h1>Login</h1>
          </div>
        </div>
      </div>
    </div>
  </header>



  <hr>

  <!-- Footer -->
  <footer><!-- Main Content -->
  <div class="container"> <!-- crea contenitore x la larghezza del layout -->
    <div class="row"> <!--  crea riga  -->
      <div class="col-lg-8 col-md-10 mx-auto"> <!-- crea colonna -->
        <p>Login zu dem privaten Area dieses Blogs</p>

        <form action = "" method = "POST" name="sentMessage" id="contactForm" novalidate>
          <div class="control-group">
            <div class="form-group floating-label-form-group controls">

              <label>Username</label>

                 <input name = "username" type="text" class="form-control" placeholder="Username" id="Username" required data-validation-required-message="Please enter your username." value ="{{ request.form.username }}">
                 <p class="help-block text-danger"></p>
                </div>
               </div>
          <div class="control-group">
            <div class="form-group floating-label-form-group controls">
              <label>Password</label>

              <input name= "pw"  class="input" placeholder="pw" id="pw" required data-validation-required-message="Please enter your password." value = "{{ request.form.pw }}">

              <p class="help-block text-danger"></p>
            </div>
          </div>

          <div class = "container">
          <div class = "row" >
          <input type = "checkbox" onclick = "Toggle()">

              <label> Show Password </label>

            <script>

                function Toggle() {
                var temp = document.getElementById("typepass");
                if (temp.type === "password") {
                temp.type = "text";
              }
                else {
                  temp.type = "password";
                }

              }

            </script>

          </div>
        </div>





          <br>
          <div id="success"></div>
          <button type="submit" class="btn btn-primary" id="sendMessageButton">Login!</button>
        </form>

        {% if error %}

        <p class = "error"> <strong> Error: </strong> {{ error }}

          {% endif %}

      </div>
    </div>
  </div>
    <div class="container">
      <div class="row">
        <div class="col-lg-8 col-md-10 mx-auto">
          <!-- Qua inizia la parte inferiore-->
          <p class="copyright text-muted">Copyright &copy; Your Website 2020</p>
        </div>
      </div>
    </div>
  </footer>



  <!-- Bootstrap core JavaScript -->
  <script src= "{{ url_for('static', filename = 'jquery.min.js')}}"> </script>
  <script src= "{{ url_for('static', filename = 'bootstrap.min.js')}}"></script>


  <!-- Custom scripts for this template -->
  <script src="{{ url_for('static', filename = 'clean-blog.min.js')}}"> </script>>

</body>

</html>

I am very new to everything and I am pretty stuck and confused at the moment. Thank you very much for every suggestion.

2
As a general rule of thumb - whenever you encounter an error, please always post the complete traceback, not only the very last line. The traceback offers valuable information e.g. where the error happened, what it's cause and what is happening. Please update your question above with the traceback, properly formatted. Thank you!J.G.
Ok thank you. Done.Franz Biberkopf
Can you write what is the content of ` File "C:\Users\franc\OneDrive\Desktop\Blog_Project\blog_prova.py", line 102, in login` ? Don't be afraid to read tracebacks - there is much information in them. You could also debug your application with pdb - if you cannot do this yet, I gave a 5 minute lightning talk how to do this. youtube.com/…J.G.

2 Answers

2
votes

The error is probably caused by user.is_authenticated = request.form["pw"] == users[username]["pw"], especially by request.form["pw"] - this tries to access the pw variable, but this was not transmitted by the form.

Maybe this is caused by the space after the equal sign - input name= "pw

Actually, I am not 100% sure, as I never used a space there. It is a good practice to use an IDE which properly formats the code. With "clean" code and less noise it is always easier to spot errors.

Update

Looks like spaces are allowed: Using spaces before and after the `=` sign of HTML element attribute

P.S.: Maybe the error is related to your usage of the request_loader. I am a heavy user of flask-login and I never used request_loader. As stated it is a special form, which you usually do not need.

I'd recommend that you read the documentation again ( https://flask-login.readthedocs.io/en/latest/#custom-login-using-request-loader ) and also have a look at this fantastic tutorial: https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-i-hello-world

Update 2

I read the flask-login documentation. When you use the request_loader you need to send the login data with every request, but you only send it when you fill in the form.

=> As earlier said. Don't use the request loader. Use eg cookie based login. See Flask Login docu and the mega tutorial.

1
votes

You should have database to store the content of blogs. Then why you want to expose in .yaml your credentials (never ever expose credentials in files that are included in git repository for historical reasons)? I would use e.g. flask_login or flask_user and create a user with a script in production (other environments) e.g.

python create_account.py --username=username --password=password

so no one could create account, then the username/password validation would be handled by either of that library and you would not expose it (in case some one will access your git repository or like so).

At least I would use this approach.