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 1 commit
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 = ""
14 changes: 9 additions & 5 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,23 @@
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
@app.before_request
def initialize_server():
# The following line will remove this handler, making it
oscie57 marked this conversation as resolved.
Show resolved Hide resolved
# only run on the first request
app.before_request_funcs[None].remove(initialize_server)

# Ensure our database is present.
db.create_all()

Expand Down
36 changes: 0 additions & 36 deletions models.py
Original file line number Diff line number Diff line change
@@ -1,19 +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):
impl = sqlalchemy.Text()
Expand Down Expand Up @@ -88,29 +78,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
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.

57 changes: 16 additions & 41 deletions thepantry/admin.py
Original file line number Diff line number Diff line change
@@ -1,66 +1,41 @@
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")
@app.route("/thepantry/")
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
28 changes: 2 additions & 26 deletions thepantry/forms.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,8 @@
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
13 changes: 6 additions & 7 deletions thepantry/items.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
from food import app, db
from models import ItemList
from flask import render_template, send_from_directory, redirect, url_for
from flask_login import login_required
from werkzeug import exceptions
from thepantry.forms import ItemEditForm, ItemAddForm
from thepantry.encodemii import save_food_image
import os
from thepantry.operations import manage_delete_item

from thepantry.admin import oidc

@app.route("/thepantry/restaurants/<restaurant_id>/menus/<menu_code>/items")
@login_required
@oidc.require_login
def list_food_items(restaurant_id, menu_code):
items = (
ItemList.query.filter_by(menu_code=int(menu_code))
Expand All @@ -32,7 +31,7 @@ def list_food_items(restaurant_id, menu_code):
"/thepantry/restaurants/<restaurant_id>/menus/<menu_code>/items/<item_code>/edit",
methods=["GET", "POST"],
)
@login_required
@oidc.require_login
def edit_item(restaurant_id, menu_code, item_code):
form = ItemEditForm()

Expand Down Expand Up @@ -72,7 +71,7 @@ def edit_item(restaurant_id, menu_code, item_code):
"/thepantry/restaurants/<restaurant_id>/menus/<menu_code>/items/add",
methods=["GET", "POST"],
)
@login_required
@oidc.require_login
def add_item(restaurant_id, menu_code):
form = ItemAddForm()

Expand Down Expand Up @@ -101,7 +100,7 @@ def add_item(restaurant_id, menu_code):
"/thepantry/restaurants/<restaurant_id>/menus/<menu_code>/items/<item_code>/delete",
methods=["GET", "POST"],
)
@login_required
@oidc.require_login
def remove_item(restaurant_id, menu_code, item_code):
def drop_item():
item = ItemList.query.filter_by(item_code=item_code).first()
Expand All @@ -118,6 +117,6 @@ def drop_item():


@app.route("/thepantry/restaurants/items/<restaurant_id>/<item_code>.jpg")
@login_required
@oidc.require_login
def get_food_image(restaurant_id, item_code):
return send_from_directory(f"./images/{restaurant_id}/", item_code + ".jpg")
Loading
Loading