Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add OIDC support & Flask 2.3+ fix #35

Merged
merged 3 commits into from
Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions config-example.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,18 @@
# EULA text, presented upon first launch of channel.
# You may wish to change this to read from a file.
eula_text = "Default terms and conditions."

# OpenID Connect configuration
oidc_redirect_uri = "http://localhost:8080/authorize"
oidc_client_secrets_json = {
"web": {
"client_id": "",
"client_secret": "",
"auth_uri": "",
"token_uri": "",
"userinfo_uri": "",
"issuer": "",
"redirect_uris": [oidc_redirect_uri],
}
}
oidc_logout_url = ""
13 changes: 6 additions & 7 deletions food.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from werkzeug import exceptions
from werkzeug.datastructures import ImmutableMultiDict

from models import db, login
from models import db

import config

Expand All @@ -24,19 +24,18 @@
app.config["SQLALCHEMY_DATABASE_URI"] = config.db_url
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
app.config["SECRET_KEY"] = config.secret_key
app.config["OIDC_CLIENT_SECRETS"] = config.oidc_client_secrets_json
app.config["OIDC_SCOPES"] = "openid profile"
app.config["OIDC_OVERWRITE_REDIRECT_URI"] = config.oidc_redirect_uri

# Ensure DB tables are created.
db.init_app(app)

# Ensure we're handling login.
login.init_app(app)

# Ensure the DB is able to determine migration needs.
migrate = Migrate(app, db, compare_type=True)


@app.before_first_request
def initialize_server():
with app.app_context():
# Ensure our database is present.
db.create_all()

Expand Down Expand Up @@ -64,7 +63,7 @@ def initialize_server():
"webApi_order_done": responses.order_done,
"webApi_inquiry_done": responses.inquiry_done,
"webApi_basket_delete": responses.basket_delete,
"webApi_basket_modify": responses.basket_modify
"webApi_basket_modify": responses.basket_modify,
}


Expand Down
35 changes: 0 additions & 35 deletions models.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,9 @@
import enum

from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.event import listens_for
from werkzeug.security import generate_password_hash, check_password_hash
from flask_login import UserMixin, LoginManager
import sqlalchemy, json

db = SQLAlchemy()
login = LoginManager()


@login.user_loader
def load_user(id):
return User.query.get(int(id))


class DictType(sqlalchemy.types.TypeDecorator):
Expand Down Expand Up @@ -88,29 +79,3 @@ class UserOrders(db.Model):
zip_code = db.Column(db.String, primary_key=True, nullable=False)
auth_key = db.Column(db.String)
basket = db.Column(DictType, nullable=False)


class User(db.Model, UserMixin):
# Used to login to the Admin Panel
id = db.Column(db.Integer, primary_key=True, default=1)
username = db.Column(db.String(100))
password_hash = db.Column(db.String)

def set_password(self, password):
self.password_hash = generate_password_hash(password)

def check_password(self, password):
return check_password_hash(self.password_hash, password)


@listens_for(User.__table__, "after_create")
def create_default_user(target, connection, **kw):
"""Adds a default user to The Pantry.
By default, we assume admin:admin."""
table = User.__table__
connection.execute(
table.insert().values(
username="admin",
password_hash=generate_password_hash("admin"),
)
)
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Flask>=2.2
flask-login>=0.6.3
flask-oidc>=2.2.0
flask-wtf>=1.0.1
lxml>=4.9.1
pillow>=9.2
Expand Down
19 changes: 9 additions & 10 deletions responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,8 +351,7 @@ def shop_one(request):
},
},
# Setting values to null will trigger the confirmation screen
"selList": {
},
"selList": {},
"holiday": "We're on holiday. Back soon!",
"status": {
"isOpen": 1,
Expand Down Expand Up @@ -500,14 +499,14 @@ def category_list(request):
return {
"response": {
"Pizza": formulate_restaurant(CategoryTypes.Pizza),
# "Bento": formulate_restaurant(CategoryTypes.Bento_Box),
# "Sushi": formulate_restaurant(CategoryTypes.Sushi),
# "Fish": formulate_restaurant(CategoryTypes.Fish),
#"Seafood": formulate_restaurant(CategoryTypes.Seafood),
#"American": formulate_restaurant(CategoryTypes.Western),
#"Fast": formulate_restaurant(CategoryTypes.Fast_Food),
#"Indian": formulate_restaurant(CategoryTypes.Curry),
#"Party": formulate_restaurant(CategoryTypes.Party_Food),
# "Bento": formulate_restaurant(CategoryTypes.Bento_Box),
# "Sushi": formulate_restaurant(CategoryTypes.Sushi),
# "Fish": formulate_restaurant(CategoryTypes.Fish),
# "Seafood": formulate_restaurant(CategoryTypes.Seafood),
# "American": formulate_restaurant(CategoryTypes.Western),
# "Fast": formulate_restaurant(CategoryTypes.Fast_Food),
# "Indian": formulate_restaurant(CategoryTypes.Curry),
# "Party": formulate_restaurant(CategoryTypes.Party_Food),
"Drinks": formulate_restaurant(CategoryTypes.Others),
"Other": formulate_restaurant(CategoryTypes.Test),
"Placeholder": formulate_restaurant(CategoryTypes.Others),
Expand Down
3 changes: 1 addition & 2 deletions templates/includes/sidebar.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
{% endmacro %}

<aside class="menu">
{% if current_user.is_authenticated %}
{% if g.oidc_user.logged_in %}
<p class="menu-label">
General
</p>
Expand All @@ -21,7 +21,6 @@
<p class="menu-label">
Account
</p>
{{ menu_item('Change Password', 'change_password', 'fa-key') }}
<ul class="menu-list">
<li><a href="{{ url_for('logout') }}"><i class="fas fa-sign-out-alt" style="margin-right: .75em; width: 1em; height: 1em;"></i>Logout</a></li>
</ul>
Expand Down
19 changes: 6 additions & 13 deletions templates/login.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,10 @@
{% endblock %}

{% block content %}
<form action="" method="post">
<p>
{{ form.hidden_tag() }}
{{ form.username.label(class_="label") }}
{{ form.username(size=32, class_="input") }}
<br>
<br>
{{ form.password.label(class_="label") }}
{{ form.password(size=32, class_="input") }}
</p>
<br>
<p>{{ form.submit(class_="button is-link") }}</p>
</form>
<h1 class="title">Click below to enter the pantry.</h1>
<p class="buttons" style="display: block">
<a href="{{ url_for('admin') }}">
<button class="button is-link">Login with WiiLink Internal</button>
</a>
</p>
{% endblock %}
6 changes: 1 addition & 5 deletions templates/pantry.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,12 @@
{% endblock %}

{% block content %}
<h1 class="title">Welcome, {{ current_user.username }}.</h1>
<h1 class="title">Welcome, {{ g.oidc_user.profile.name }}.</h1>
<h2 class="subtitle">Click on an operation below to get started:</h2>
<a href="{{ url_for('select_food_type') }}">
<button>Manage Resturants</button>
</a>
<br/>
<a href="{{ url_for('change_password') }}">
<button>Change Password</button>
</a>
<br/>
<br/>
<a href="{{ url_for('logout') }}">
<button>Logout</button>
Expand Down
25 changes: 0 additions & 25 deletions templates/user_pwchange.html

This file was deleted.

58 changes: 18 additions & 40 deletions thepantry/admin.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
from food import app, db
from food import app
from flask import render_template, send_from_directory, redirect, url_for, flash
from flask_login import current_user, login_user, login_required, logout_user
from models import User
from thepantry.forms import LoginForm, ChangePasswordForm
from flask_oidc import OpenIDConnect

import config

@app.login_manager.unauthorized_handler
def unauthorized():
return redirect(url_for("root"))
oscie57 marked this conversation as resolved.
Show resolved Hide resolved
oidc = OpenIDConnect(app)


@app.context_processor
def inject_oidc():
return dict(oidc=oidc)


@app.route("/thepantry")
Expand All @@ -16,51 +18,27 @@ def root():
return redirect(url_for("login"))


@app.route("/thepantry/login", methods=["GET", "POST"])
@app.route("/thepantry/login")
def login():
if current_user.is_authenticated:
if oidc.user_loggedin:
return redirect(url_for("admin"))

form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first()
if user is None or not user.check_password(form.password.data):
flash("Invalid username or password")
else:
login_user(user, remember=False)
return redirect(url_for("admin"))

return render_template("login.html", form=form)
return render_template("login.html")


@app.route("/thepantry/admin")
@login_required
@oidc.require_login
def admin():
return render_template("pantry.html")


@app.route("/thepantry/change_password", methods=["GET", "POST"])
@login_required
def change_password():
form = ChangePasswordForm()
if form.validate_on_submit():
print(type(current_user))
u = User.query.filter_by(username=current_user.username).first()
u.set_password(form.new_password.data)
db.session.add(u)
db.session.commit()
return redirect(url_for("admin"))

return render_template(
"user_pwchange.html", form=form, username=current_user.username
)


@app.route("/thepantry/logout")
@login_required
@oidc.require_login
def logout():
logout_user()
return redirect(url_for("login"))
oidc.logout()
response = redirect(config.oidc_logout_url)
response.set_cookie("session", expires=0)
return response


@app.route("/thepantry/common.css")
Expand Down
27 changes: 2 additions & 25 deletions thepantry/forms.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,9 @@
from flask_wtf import FlaskForm
from wtforms.validators import DataRequired, ValidationError
from wtforms import StringField, PasswordField, SubmitField, SelectField, FileField
from wtforms.validators import DataRequired
from wtforms import StringField, SubmitField, SelectField, FileField
from models import CategoryTypes


class LoginForm(FlaskForm):
username = StringField("Username")
password = PasswordField("Password")
submit = SubmitField("Enter the pantry")


class ChangePasswordForm(FlaskForm):
current_password = PasswordField("Password", validators=[DataRequired()])
new_password = PasswordField("New Password", validators=[DataRequired()])
new_password_confirmation = PasswordField(
"Confirm New Password", validators=[DataRequired()]
)
complete = SubmitField("Complete")

def validate_current_password(self, _):
if self.current_password.data == self.new_password.data:
return ValidationError("New password cannot be the same as current!")

def validate_new_password(self, _):
if self.new_password.data != self.new_password_confirmation.data:
return ValidationError("New passwords must be the same")


class FoodTypes(FlaskForm):
food = SelectField(
"Food Types",
Expand Down
Loading
Loading