diff --git a/app/__init__.py b/app/__init__.py index fb0ef3a..6692e23 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,7 +1,14 @@ from flask import * from flask_user import * +import flask_menu as menu +from flask.ext import markdown +from flask_github import GitHub app = Flask(__name__) app.config.from_pyfile('../config.cfg') +menu.Menu(app=app) +markdown.Markdown(app, extensions=['fenced_code']) +github = GitHub(app) + import models, views diff --git a/app/models.py b/app/models.py index 6129330..97dc3c9 100644 --- a/app/models.py +++ b/app/models.py @@ -1,5 +1,5 @@ from flask import Flask, url_for -from flask.ext.sqlalchemy import SQLAlchemy +from flask_sqlalchemy import SQLAlchemy from app import app from datetime import datetime from sqlalchemy.orm import validates @@ -22,6 +22,10 @@ class User(db.Model, UserMixin): password = db.Column(db.String(255), nullable=False, server_default='') reset_password_token = db.Column(db.String(100), nullable=False, server_default='') + # Account linking + github_username = db.Column(db.String(50), nullable=True, unique=True) + forums_username = db.Column(db.String(50), nullable=True, unique=True) + # User email information email = db.Column(db.String(255), nullable=True, unique=True) confirmed_at = db.Column(db.DateTime()) @@ -38,6 +42,7 @@ class User(db.Model, UserMixin): self.username = username self.confirmed_at = datetime.datetime.now() - datetime.timedelta(days=6000) + self.display_name = username def isClaimed(self): return self.password is not None and self.password != "" diff --git a/app/templates/flask_user/login.html b/app/templates/flask_user/login.html index 44274bb..3d36c20 100644 --- a/app/templates/flask_user/login.html +++ b/app/templates/flask_user/login.html @@ -68,9 +68,16 @@ Sign in

New here?

{% if user_manager.enable_register and not user_manager.require_invitation %} - {%trans%}Create an account{%endtrans%} + {%trans%}Create an account{%endtrans%} {% endif %} + + + {% endblock %} diff --git a/app/views/__init__.py b/app/views/__init__.py index deb5ac5..bfe8c1b 100644 --- a/app/views/__init__.py +++ b/app/views/__init__.py @@ -3,72 +3,20 @@ from flask import * from flask_user import * from flask_login import login_user, logout_user from app.models import * -from flask.ext import menu, markdown +import flask_menu as menu +from flask.ext import markdown from sqlalchemy import func from werkzeug.contrib.cache import SimpleCache cache = SimpleCache() -menu.Menu(app=app) -markdown.Markdown(app, extensions=['fenced_code']) - # TODO: remove on production! @app.route('/static/') def send_static(path): return send_from_directory('static', path) +import users, githublogin + @app.route('/') @menu.register_menu(app, '.', 'Home') def home_page(): return render_template('index.html') - -# Define the User registration form -# It augments the Flask-User RegisterForm with additional fields -from flask_user.forms import RegisterForm -from flask_wtf import FlaskForm -from wtforms import StringField, SubmitField, validators -class MyRegisterForm(RegisterForm): - first_name = StringField('First name', validators=[ - validators.DataRequired('First name is required')]) - last_name = StringField('Last name', validators=[ - validators.DataRequired('Last name is required')]) - -# Define the User profile form -class UserProfileForm(FlaskForm): - first_name = StringField('First name', validators=[ - validators.DataRequired('First name is required')]) - last_name = StringField('Last name', validators=[ - validators.DataRequired('Last name is required')]) - submit = SubmitField('Save') - -@app.route('/user/', methods=['GET', 'POST']) -@app.route('/user//', methods=['GET']) -def user_profile_page(username=None): - user = None - form = None - if username is None: - if not current_user.is_authenticated: - return current_app.login_manager.unauthorized() - user = current_user - else: - user = User.query.filter_by(username=username).first() - if not user: - abort(404) - - if user == current_user: - # Initialize form - form = UserProfileForm(request.form, current_user) - - # Process valid POST - if request.method=='POST' and form.validate(): - # Copy form fields to user_profile fields - form.populate_obj(current_user) - - # Save user_profile - db.session.commit() - - # Redirect to home page - return redirect(url_for('home_page')) - - # Process GET or invalid POST - return render_template('users/user_profile_page.html', - user=user, form=form) diff --git a/app/views/githublogin.py b/app/views/githublogin.py new file mode 100644 index 0000000..327fa8e --- /dev/null +++ b/app/views/githublogin.py @@ -0,0 +1,100 @@ +from flask import * +from flask_user import * +from flask_login import login_user, logout_user +import flask_menu as menu +from flask_github import GitHub +from app import app, github +from app.models import * + + +@app.route('/user/github/start/') +def github_signin_page(): + return github.authorize("public_repo,repo") + + +def _do_login_user(user, remember_me=False): + def _call_or_get(v): + if callable(v): + return v() + else: + return v + + # User must have been authenticated + if not user: + return False + + user.active = True + db.session.commit() + + # Check if user account has been disabled + if not _call_or_get(user.is_active): + flash('Your account has not been enabled.', 'error') + return False + + # Check if user has a confirmed email address + user_manager = current_app.user_manager + if user_manager.enable_email and user_manager.enable_confirm_email \ + and not current_app.user_manager.enable_login_without_confirm_email \ + and not user.has_confirmed_email(): + url = url_for('user.resend_confirm_email') + flash("Your email address has not yet been confirmed", 'error') + return False + + # Use Flask-Login to sign in user + login_user(user, remember=remember_me) + signals.user_logged_in.send(current_app._get_current_object(), user=user) + + flash('You have signed in successfully.', 'success') + + return True + + + +def _login_user(user): + user_mixin = None + if user_manager.enable_username: + user_mixin = user_manager.find_user_by_username(user.username) + + return _do_login_user(user_mixin, False) + + + +@app.route('/user/github/callback/') +@github.authorized_handler +def github_authorized(oauth_token): + next_url = request.args.get('next') + if oauth_token is None: + flash("Authorization failed [err=gh-oauth-login-failed]", "danger") + return redirect(url_for("user.login")) + + import requests + + # Get Github username + url = "https://api.github.com/user" + r = requests.get(url, headers={"Authorization": "token " + oauth_token}) + username = r.json()["login"] + + # Get user by github username + userByGithub = User.query.filter_by(github_username=username).first() + + # If logged in, connect + if current_user and current_user.is_authenticated: + if userByGithub is None: + current_user.github_username = username + db.session.add(auth) + db.session.commit() + return redirect(url_for("gitAccount", id=auth.id)) + else: + flash("Github account is already associated with another user", "danger") + return redirect(url_for("home_page")) + + # If not logged in, log in + else: + if userByGithub is None: + flash("Authorization failed [err=gh-no-such-account]", "danger") + return redirect(url_for("user.login")) + elif _login_user(userByGithub): + return redirect(next_url or url_for("home_page")) + else: + flash("Authorization failed [err=gh-login-failed]", "danger") + return redirect(url_for("user.login")) diff --git a/app/views/users.py b/app/views/users.py new file mode 100644 index 0000000..99cf19c --- /dev/null +++ b/app/views/users.py @@ -0,0 +1,60 @@ +from flask import * +from flask_user import * +from flask_login import login_user, logout_user +from flask.ext import menu +from app import app +from app.models import * + + + +# Define the User registration form +# It augments the Flask-User RegisterForm with additional fields +from flask_user.forms import RegisterForm +from flask_wtf import FlaskForm +from wtforms import StringField, SubmitField, validators +class MyRegisterForm(RegisterForm): + first_name = StringField('First name', validators=[ + validators.DataRequired('First name is required')]) + last_name = StringField('Last name', validators=[ + validators.DataRequired('Last name is required')]) + +# Define the User profile form +class UserProfileForm(FlaskForm): + first_name = StringField('First name', validators=[ + validators.DataRequired('First name is required')]) + last_name = StringField('Last name', validators=[ + validators.DataRequired('Last name is required')]) + submit = SubmitField('Save') + +@app.route('/user/', methods=['GET', 'POST']) +@app.route('/user//', methods=['GET']) +def user_profile_page(username=None): + user = None + form = None + if username is None: + if not current_user.is_authenticated: + return current_app.login_manager.unauthorized() + user = current_user + else: + user = User.query.filter_by(username=username).first() + if not user: + abort(404) + + if user == current_user: + # Initialize form + form = UserProfileForm(request.form, current_user) + + # Process valid POST + if request.method=='POST' and form.validate(): + # Copy form fields to user_profile fields + form.populate_obj(current_user) + + # Save user_profile + db.session.commit() + + # Redirect to home page + return redirect(url_for('home_page')) + + # Process GET or invalid POST + return render_template('users/user_profile_page.html', + user=user, form=form) diff --git a/config.example.cfg b/config.example.cfg index 7fffe2c..05fa9be 100644 --- a/config.example.cfg +++ b/config.example.cfg @@ -4,3 +4,6 @@ SECRET_KEY="" WTF_CSRF_SECRET_KEY="" SQLALCHEMY_DATABASE_URI = "sqlite:///../db.sqlite" + +GITHUB_CLIENT_ID = "" +GITHUB_CLIENT_SECRET = "" diff --git a/requirements.txt b/requirements.txt index dd7f72c..0fdd36f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ Flask-SQLAlchemy>=2.3 Flask-User>=0.6.19 Flask-Menu>=0.7.0 Flask-Markdown>=0.3 +GitHub-Flask>=3.2.0 diff --git a/setup.py b/setup.py index 0ba2b5a..9949776 100644 --- a/setup.py +++ b/setup.py @@ -6,12 +6,15 @@ if delete_db and os.path.isfile("app/data.sqlite"): os.remove("app/data.sqlite") if not os.path.isfile("app/data.sqlite"): - from app import models + from app.models import * print("Creating database tables...") - models.db.create_all() - + db.create_all() print("Filling database...") - models.db.session.commit() + + ruben = User("rubenwardy") + ruben.github_username = "rubenwardy" + db.session.add(ruben) + db.session.commit() else: print("Database already exists")