· 8 years ago · Mar 26, 2018, 07:42 AM
1import os
2import base64
3from io import BytesIO
4from flask import Flask, render_template, request, redirect, url_for, flash, session,
5 abort
6from werkzeug.security import generate_password_hash, check_password_hash
7from flask_sqlalchemy import SQLAlchemy
8from flask_login import LoginManager, UserMixin, login_user, logout_user,
9 current_user
10from flask_bootstrap import Bootstrap
11from flask_wtf import FlaskForm
12from wtforms import StringField, PasswordField, SubmitField, SelectField
13from wtforms.validators import Required, Length, EqualTo
14import onetimepass
15import pyqrcode
16from datetime import datetime, date, time, timedelta
17from sqlalchemy import Table, Column, Integer, ForeignKey
18from sqlalchemy.orm import relationship
19from sqlalchemy.ext.declarative import declarative_base
20from flask_security import Security, SQLAlchemyUserDatastore,
21 UserMixin, RoleMixin, login_required
22
23# create application instance
24app = Flask(__name__)
25app.config.from_object('config')
26
27# initialize extensions
28bootstrap = Bootstrap(app)
29db = SQLAlchemy(app)
30lm = LoginManager(app)
31Base = declarative_base()
32
33# Define models
34roles_users = db.Table('roles_users',
35 db.Column('users_id', db.Integer(), db.ForeignKey('users.id')),
36 db.Column('role_id', db.Integer(), db.ForeignKey('role.id')))
37
38class Role(db.Model, RoleMixin):
39 id = db.Column(db.Integer(), primary_key=True)
40 name = db.Column(db.String(80), unique=True)
41 description = db.Column(db.String(255))
42
43class User(UserMixin, db.Model):
44 """User model."""
45 __tablename__ = 'users'
46 id = db.Column(db.Integer, unique=True, nullable=False, primary_key=True)
47 username = db.Column(db.String(64), unique=True, index=True)
48 password_hash = db.Column(db.String(128))
49 otp_secret = db.Column(db.String(16))
50 access = db.Column(db.String(16))
51 roles = db.relationship('Role', secondary=roles_users,
52 backref=db.backref('users', lazy='dynamic'))
53
54 def __init__(self, **kwargs):
55 super(User, self).__init__(**kwargs)
56 if self.otp_secret is None:
57 # generate a random secret
58 self.otp_secret = base64.b32encode(os.urandom(10)).decode('utf-8')
59
60 @property
61 def password(self):
62 raise AttributeError('password is not a readable attribute')
63
64 @password.setter
65 def password(self, password):
66 self.password_hash = generate_password_hash(password)
67
68 def verify_password(self, password):
69 return check_password_hash(self.password_hash, password)
70
71 def get_totp_uri(self):
72 return 'otpauth://totp/2FA-Demo:{0}?secret={1}&issuer=2FA-Demo'
73 .format(self.username, self.otp_secret)
74
75 def verify_totp(self, token):
76 return onetimepass.valid_totp(token, self.otp_secret)
77
78################################################################################
79user_datastore = SQLAlchemyUserDatastore(db, User, Role)
80security = Security(app, user_datastore)
81###############################################################################
82
83@lm.user_loader
84def load_user(user_id):
85 """User loader callback for Flask-Login."""
86 return User.query.get(int(user_id))
87
88class RegisterForm(FlaskForm):
89 """Registration form."""
90 access = SelectField(validators=[Required()],
91 choices=[("dm", "demo"),
92 ("dm2", "demo2")])
93 username = StringField('Username', validators=[Required(), Length(1, 9)])
94 password = PasswordField('Password', validators=[Required(), Length(4, 4)])
95 password_again = PasswordField('Password again',
96 validators=[Required(), EqualTo('password')])
97 submit = SubmitField('Register')
98
99
100class LoginForm(FlaskForm):
101 """Login form."""
102 username = StringField('Username', validators=[Required(), Length(1, 9)])
103 password = PasswordField('Password', validators=[Required(), Length(4, 4)])
104 token = PasswordField('Token', validators=[Required(), Length(6, 6)])
105 submit = SubmitField('Login')
106
107@app.route('/')
108def index():
109 return render_template('index.html')
110
111
112@app.route('/register', methods=['GET', 'POST'])
113def register():
114 """User registration route."""
115 if current_user.is_authenticated:
116 # if user is logged in we get out of here
117 return redirect(url_for('index'))
118 form = RegisterForm()
119 if form.validate_on_submit():
120 user = User.query.filter_by(username=form.username.data).first()
121 if user is not None:
122 flash('Username already exists.')
123 return redirect(url_for('register'))
124 # add new user to the database
125 user = User(username=form.username.data, password=form.password.data, access=form.access.data)
126 db.session.add(user)
127 db.session.commit()
128
129 # redirect to the two-factor auth page, passing username in session
130 session['username'] = user.username
131 return redirect(url_for('two_factor_setup'))
132 return render_template('auth/register.html', form=form)
133
134
135@app.route('/twofactor')
136def two_factor_setup():
137 if 'username' not in session:
138 return redirect(url_for('index'))
139 user = User.query.filter_by(username=session['username']).first()
140 if user is None:
141 return redirect(url_for('index'))
142 # since this page contains the sensitive qrcode, make sure the browser
143 # does not cache it
144 return render_template('auth/2fa.html'), 200, {
145 'Cache-Control': 'no-cache, no-store, must-revalidate',
146 'Pragma': 'no-cache',
147 'Expires': '0'}
148
149
150@app.route('/qrcode')
151def qrcode():
152 if 'username' not in session:
153 abort(404)
154 user = User.query.filter_by(username=session['username']).first()
155 if user is None:
156 abort(404)
157
158 # for added security, remove username from session
159 del session['username']
160
161 # render qrcode for FreeTOTP
162 url = pyqrcode.create(user.get_totp_uri())
163 stream = BytesIO()
164 url.svg(stream, scale=3)
165 return stream.getvalue(), 200, {
166 'Content-Type': 'image/svg+xml',
167 'Cache-Control': 'no-cache, no-store, must-revalidate',
168 'Pragma': 'no-cache',
169 'Expires': '0'}
170
171
172@app.route('/login', methods=['GET', 'POST'])
173def login():
174 """User login route."""
175 if current_user.is_authenticated:
176 # if user is logged in we get out of here
177 return redirect(url_for('index'))
178 form = LoginForm()
179 if form.validate_on_submit():
180 user = User.query.filter_by(username=form.username.data).first()
181 if user is None or not user.verify_password(form.password.data) or
182 not user.verify_totp(form.token.data):
183 flash('Invalid username, password or token.')
184 return redirect(url_for('login'))
185
186 # log user in
187 login_user(user)
188 flash('You are now logged in!')
189 return redirect(url_for('cm_e_11a'))
190 return render_template('auth/login.html', form=form)
191
192
193@app.route('/logout')
194def logout():
195 """User logout route."""
196 logout_user()
197 return redirect(url_for('index'))
198
199<h1>Login</h1>
200<form action="/login" method="POST" name="login_user_form">
201 <input id="next" name="next" type="hidden" value="">
202<input id="csrf_token" name="csrf_token" type="hidden" value="IjEyNWY4ZjliNmIxZTJjNmM3YmM4YzFlM2U0NWZlYjJlMDEzYThiNWEi.DZnvqw.key_C7NmtQogY7CSpb14sfVDUOQ">
203
204 <p>
205 <label for="email">Email Address</label> <input id="email" name="email" type="text" value="">
206
207 </p>
208
209
210 <p>
211 <label for="password">Password</label> <input id="password" name="password" type="password" value="">
212
213 </p>
214
215
216 <p>
217 <label for="remember">Remember Me</label> <input id="remember" name="remember" type="checkbox" value="y">
218
219 </p>
220
221
222 <p><input id="next" name="next" type="hidden" value=""></p>
223
224
225 <p><input id="submit" name="submit" type="submit" value="Login"></p>
226
227</form>