Skip to content
Open
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
13 changes: 13 additions & 0 deletions backend/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from authlib.integrations.flask_client import OAuth
from flask import Flask
from flask_cors import CORS
from flask_jwt_extended import JWTManager
Expand Down Expand Up @@ -44,6 +45,7 @@
safe_url = URLSafeTimedSerializer(SECRET_KEY)
ma = Marshmallow()
migrate = Migrate(app, db)
oauth = OAuth(app)
# CORS(app, supports_credentials=True, resources={r"/*": {"origins": ["http://localhost:3000"]}})
CORS(app, supports_credentials=True, resources={r"/*": {"origins": ["*"]}})
git = Github(GITHUB_ACCESS_TOKEN)
Expand All @@ -55,6 +57,17 @@
secret=PUSHER_SECRET,
cluster=PUSHER_CLUSTER,
ssl=True)
auth0 = oauth.register(
'auth0',
client_id=AUTH0_CLIENT_ID,
client_secret=AUTH0_CLIENT_SECRET,
api_base_url=AUTH0_URL,
access_token_url=AUTH0_ACCESS_TOK_URL,
authorize_url=AUTH0_AUTH_URL,
client_kwargs={
'scope': 'openid profile email',
},
)

from backend.models import User

Expand Down
5 changes: 2 additions & 3 deletions backend/activities/decorators.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from flask import request
from flask_jwt_extended import get_jwt_identity
from flask import (request, session)
from backend.activities.schemas import activity_form_schema
from backend.models import Activity, Student
from functools import wraps
Expand Down Expand Up @@ -43,7 +42,7 @@ def activity_exists_in_student_prog(f):
@wraps(f)
def wrap(*args, **kwargs):
activity_completed = request.get_json()
username = get_jwt_identity()
username = session["profile"]["username"]
student = Student.query.filter_by(username=username).first()
activity_id = activity_completed["complete"]["id"]
activity = Activity.query.get(activity_id)
Expand Down
6 changes: 3 additions & 3 deletions backend/activities/routes.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
from flask import (Blueprint, request)
from flask_jwt_extended import jwt_required
from flask_restful import Resource
from backend import api, db
from backend.activities.decorators import activity_exists, activity_exists_in_github, valid_activity_form
from backend.activities.schemas import activity_schema, activities_schema
from backend.activities.utils import create_activity, edit_activity
from backend.authentication.decorators import auth0_auth
from backend.hints.utils import sort_hints
from backend.models import Activity

Expand Down Expand Up @@ -52,7 +52,7 @@ def delete(self):

# Class to get all tracks
class ActivityFetchAll(Resource):
method_decorators = [jwt_required]
method_decorators = [auth0_auth]

# Function to get all activities
def get(self):
Expand All @@ -63,7 +63,7 @@ def get(self):

# This class is used to get a specific activity based on id
class ActivityGetSpecific(Resource):
method_decorators = [jwt_required, activity_exists]
method_decorators = [auth0_auth, activity_exists]

def get(self, activity_id):
activity = Activity.query.get(activity_id)
Expand Down
5 changes: 2 additions & 3 deletions backend/activity_progresses/decorators.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from flask import request
from flask_jwt_extended import get_jwt_identity
from flask import (request, session)
from backend.activity_progresses.schemas import activity_progress_grading_schema
from backend.models import Activity, ActivityProgress, Student
from functools import wraps
Expand All @@ -9,7 +8,7 @@
def activity_prog_exists(f):
@wraps(f)
def wrap(*args, **kwargs):
username = get_jwt_identity()
username = session["profile"]["username"]
student = Student.query.filter_by(username=username).first()
student_activity_prog = ActivityProgress.query.filter_by(student_id=student.id,
activity_id=kwargs['activity_id']).first()
Expand Down
9 changes: 4 additions & 5 deletions backend/activity_progresses/routes.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from flask import Blueprint
from flask_jwt_extended import get_jwt_identity
from flask import (Blueprint, session)
from flask_restful import Resource
from backend import api, db
from backend.authentication.decorators import roles_accepted
Expand All @@ -23,7 +22,7 @@ class ActivityProgressUpdate(Resource):
# Function to return the last card completed on an activity
@cards_exist_in_activity
def get(self, activity_id):
username = get_jwt_identity()
username = session["profile"]["username"]
student = Student.query.filter_by(username=username).first()
student_activity_prog = ActivityProgress.query.filter_by(student_id=student.id,
activity_id=activity_id).first()
Expand Down Expand Up @@ -51,7 +50,7 @@ def get(self, activity_id):

@activity_prog_exists
def delete(self, activity_id):
username = get_jwt_identity()
username = session["profile"]["username"]
student = Student.query.filter_by(username=username).first()
student_activity_prog = ActivityProgress.query.filter_by(student_id=student.id,
activity_id=activity_id).first()
Expand All @@ -70,7 +69,7 @@ class ActivityProgressHints(Resource):
# Function to unlock a hint by its hint_id
@activity_prog_exists
def put(self, activity_id, hint_id):
username = get_jwt_identity()
username = session["profile"]["username"]
student = Student.query.filter_by(username=username).first()
student_activity_prog = ActivityProgress.query.filter_by(student_id=student.id,
activity_id=activity_id).first()
Expand Down
21 changes: 13 additions & 8 deletions backend/authentication/decorators.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from flask import request
from flask_jwt_extended import verify_jwt_in_request, get_jwt_claims
from flask import request, redirect, session
from flask_praetorian.exceptions import MissingRoleError
from backend import guard
from backend.authentication.schemas import user_form_schema, user_login_schema
Expand All @@ -8,16 +7,24 @@
from functools import wraps


# Used to authenticate users signing in with auth0
def auth0_auth(f):
@wraps(f)
def decorator(*args, **kwargs):
if 'profile' not in session:
return redirect('/auth')
return f(*args, **kwargs)
return decorator


# Took inspiration from flask praetorian roles accepted decorator
# This is used to restrict a route to accept certain roles
def roles_accepted(*accepted_rolenames):
def decorator(method):
@wraps(method)
def wrapper(*args, **kwargs):
role_set = set([str(n) for n in accepted_rolenames])
verify_jwt_in_request()
claims = get_jwt_claims()
user_roles = set(r.strip() for r in claims['roles'].split(','))
user_roles = {session["roles"]}
try:
MissingRoleError.require_condition(
not user_roles.isdisjoint(role_set),
Expand All @@ -38,9 +45,7 @@ def decorator(f):
@wraps(f)
def wrapper(*args, **kwargs):
role_set = set([str(n) for n in required_rolenames])
verify_jwt_in_request()
claims = get_jwt_claims()
user_roles = set(r.strip() for r in claims['roles'].split(','))
user_roles = {session["roles"]}

try:
MissingRoleError.require_condition(
Expand Down
62 changes: 57 additions & 5 deletions backend/authentication/routes.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,37 @@
from flask import (Blueprint, jsonify, request)
from backend.config import API, AUTH0_CLIENT_ID
from flask import (Blueprint, jsonify, redirect, request, session)
from flask_jwt_extended import create_access_token, get_csrf_token, get_jwt_identity, jwt_required, set_access_cookies, \
unset_jwt_cookies
from flask_restful import Resource
from backend import api, db, jwt, safe_url
from backend.authentication.utils import create_user, send_verification_email
from backend import api, auth0, db, jwt, oauth, safe_url
from backend.authentication.utils import create_user, send_verification_email, store_user
from backend.badges.utils import create_student_badges
from backend.authentication.decorators import roles_required, user_exists, user_is_active, valid_user_form, \
valid_user_type
from backend.models import Badge, User
from backend.modules.utils import create_module_progresses
from itsdangerous import SignatureExpired
from six.moves.urllib.parse import urlencode
import requests

# Blueprint for users
authentication_bp = Blueprint("authentication", __name__)


# Class to redirect to auth0
class UserAuth0(Resource):
def get(self):
return auth0.authorize_redirect(redirect_uri=API + "/auth/callback")


# Class to logout using auth0
class UserAuth0Logout(Resource):
def get(self):
session.clear()
params = {'returnTo': API + "/auth", 'client_id': AUTH0_CLIENT_ID}
return redirect(auth0.api_base_url + '/v2/logout?' + urlencode(params))


class UserAuthorize(Resource):
# Route to confirm that the email is real
def get(self, token):
Expand All @@ -37,6 +53,37 @@ def get(self, token):
}, 200


# Callback route to get data from auth0
class UserCallBack(Resource):
def get(self):
auth0.authorize_access_token()
resp = auth0.get('userinfo')
userinfo = resp.json()


session['jwt_payload'] = userinfo
session['profile'] = {
'name': userinfo['name'],
'image': userinfo['picture'],
'username': userinfo['email']
}
# Hard coding this data for now
userinfo["track_id"] = 1
userinfo["location"] = "Davis"
# I think is_active is now unneccessary
userinfo["is_active"] = True
userinfo["password"] = "TemporaryPW"

# If user doesn't exist store in db
cur_user = User.query.filter_by(username=userinfo["email"]).first()
if (cur_user is None):
cur_user = store_user(userinfo)

session["roles"] = cur_user.roles

return redirect('/')


# Class to create a user
class UserCreate(Resource):
method_decorators = [valid_user_form, valid_user_type]
Expand Down Expand Up @@ -134,7 +181,9 @@ def get(self):

class Ping(Resource):
def get(self):
return jsonify({"message": "pong"})
userinfo = session["roles"]
return userinfo
#return jsonify({"message": "pong"})


@jwt.user_claims_loader
Expand All @@ -147,12 +196,15 @@ def add_claims_to_access_token(identity):


# Creates the routes for the classes
api.add_resource(UserAuth0, "/auth")
api.add_resource(UserAuth0Logout, "/auth/logout")
api.add_resource(UserAuthorize, "/confirm_email/<string:token>")
api.add_resource(UserCallBack, "/auth/callback")
api.add_resource(UserCreate, "/users/<string:user_type>/create")
api.add_resource(UserLoginHandler, "/user/login")
api.add_resource(UserLogoutHandler, "/user/logout")
api.add_resource(Protected, "/protected")
api.add_resource(UserIsAdmin, "/isAdmin")
api.add_resource(UserIsStudent, "/isStudent")
api.add_resource(UserIsTeacher, "/isTeacher")
api.add_resource(Ping, "/ping")
api.add_resource(Ping, "/ping")
21 changes: 16 additions & 5 deletions backend/authentication/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,17 @@ def create_student(form_data):
hashed_password = guard.hash_password(form_data["password"])

student = Student(name=form_data["name"],
username=form_data["username"],
username=form_data["email"],
password=hashed_password,
roles="Student",
location=form_data["location"],
is_active=False,
image=form_data["image"],
is_active=form_data["is_active"],
image=form_data["picture"],
current_track_id=form_data["track_id"]
)

classroom = Classroom.query.filter_by(class_code=form_data["class_code"]).first()
student.classes.append(classroom)
#classroom = Classroom.query.filter_by(class_code=form_data["class_code"]).first()
#student.classes.append(classroom)
track = Track.query.get(form_data["track_id"])
student.incomplete_topics = track.topics
student.incomplete_modules = assign_incomplete_modules(track.topics)
Expand Down Expand Up @@ -107,3 +107,14 @@ def send_graded_activity_email(email):
mail.send(msg)

return


# Function to store the new users in the database
def store_user(userinfo):
user = create_user("Student", userinfo)

db.session.add(user)
db.session.commit()
send_verification_email(user.username)

return user
4 changes: 2 additions & 2 deletions backend/badges/routes.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from flask import (Blueprint, request)
from flask_jwt_extended import jwt_required
from flask_restful import Resource
from backend import api, db
from backend.authentication.decorators import auth0_auth
from backend.badges.decorators import badge_delete, badge_exists, badge_exists_in_contentful
from backend.badges.schemas import badge_schema
from backend.badges.utils import create_badge, edit_badge
Expand Down Expand Up @@ -54,7 +54,7 @@ def post(self):

# Function to get a specific Badge based on badge id
class BadgeGetSpecific(Resource):
method_decorators = [badge_exists, jwt_required]
method_decorators = [badge_exists, auth0_auth]

def get(self, badge_id):
badge = Badge.query.get(badge_id)
Expand Down
5 changes: 2 additions & 3 deletions backend/cards/decorators.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from flask import request
from flask_jwt_extended import get_jwt_identity
from flask import (request, session)
from backend.cards.schemas import card_form_schema
from backend.models import Activity, ActivityProgress, Card, Student
from functools import wraps
Expand Down Expand Up @@ -59,7 +58,7 @@ def wrap(*args, **kwargs):
def card_is_unlockable(f):
@wraps(f)
def wrap(*args, **kwargs):
username = get_jwt_identity()
username = session["profile"]["username"]
student = Student.query.filter_by(username=username).first()
card = Card.query.get(kwargs['card_id'])
student_activity_prog = ActivityProgress.query.filter_by(student_id=student.id,
Expand Down
11 changes: 5 additions & 6 deletions backend/cards/routes.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
from flask import (Blueprint, request)
from flask_jwt_extended import get_jwt_identity, jwt_required
from flask import (Blueprint, request, session)
from flask_restful import Resource
from backend import api, db
from backend.authentication.decorators import roles_accepted
from backend.activity_progresses.utils import unlock_card
from backend.authentication.decorators import roles_accepted, auth0_auth
from backend.cards.decorators import card_exists, card_exists_in_activity, card_exists_in_github, card_is_unlockable, \
valid_card_form
from backend.cards.schemas import card_schema
Expand Down Expand Up @@ -59,7 +58,7 @@ def delete(self):

# This class is used to get a specific card based on id
class CardGetSpecific(Resource):
method_decorators = [jwt_required, card_exists]
method_decorators = [auth0_auth, card_exists]

# Function to return data on a single card
def get(self, card_id):
Expand All @@ -74,7 +73,7 @@ class CardGetHints(Resource):

# Function to return data on the HintStatus
def get(self, activity_id, card_id):
username = get_jwt_identity()
username = session["profile"]["username"]
student = Student.query.filter_by(username=username).first()
student_activity_prog = ActivityProgress.query.filter_by(student_id=student.id,
activity_id=activity_id).first()
Expand All @@ -86,7 +85,7 @@ def get(self, activity_id, card_id):
# Function to unlock the next card
@card_is_unlockable
def put(self, activity_id, card_id):
username = get_jwt_identity()
username = session["profile"]["username"]
student = Student.query.filter_by(username=username).first()
card = Card.query.get(card_id)
student_activity_prog = ActivityProgress.query.filter_by(student_id=student.id,
Expand Down
Loading