Skip to content

Commit 641560f

Browse files
committed
Conflicts: app/jekylledit/model/site.py
2 parents 80c9330 + 20ef3f4 commit 641560f

File tree

14 files changed

+168
-163
lines changed

14 files changed

+168
-163
lines changed

Diff for: app/jekylledit/controllers/auth.py

+77-112
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from ..ext.identitytoolkit import Gitkit
1414
from itsdangerous import URLSafeTimedSerializer
1515

16-
from ..model import Account, Repository, Roles, Site, db
16+
from ..model import Account, Challenge, Repository, Roles, Site, Sites, db
1717
from .base import app, mailgun, jsonp
1818

1919

@@ -70,6 +70,8 @@ def authorization_required(*roles):
7070
def decorator(func):
7171
@wraps(func)
7272
def wrapper(**values):
73+
if not current_user.email_verified:
74+
abort(403)
7375
site_id = values['site_id']
7476
synchronize(site_id)
7577
needs = [(role, site_id) for role in roles]
@@ -84,26 +86,11 @@ def permission_denied(exc):
8486
return 'Forbidden', 403
8587

8688

87-
@app.template_global()
88-
def auth_url_for(endpoint, **values):
89-
if endpoint == 'widget':
90-
if app.config['DEVELOPMENT']:
91-
values.setdefault('next', request.url)
92-
return url_for('auth.widget', **values)
93-
next = values.pop('next', request.url)
94-
values['next'] = url_for(
95-
'auth.sign_in_success',
96-
next=next,
97-
_external=True)
98-
return url_for('auth.widget', **values)
99-
if endpoint == 'sign_out':
100-
return url_for('auth.sign_out', **values)
101-
raise Exception('Invalid enpoint: {}'.format(endpoint))
102-
103-
10489
@blueprint.route('/widget', methods={'GET', 'POST'})
10590
def widget():
106-
if app.config['DEVELOPMENT'] and request.method == 'POST':
91+
if app.config['DEVELOPMENT']:
92+
if request.method == 'GET':
93+
return render_template('auth/widget.html')
10794
email = request.form['email']
10895
account = Account.query.filter_by(email=email).one_or_none()
10996
if account is None:
@@ -115,43 +102,40 @@ def widget():
115102
db.session.flush()
116103
login_user(account)
117104
db.session.commit()
118-
return redirect(request.args.get('next', request.url_root))
119-
if not app.config['DEVELOPMENT']:
120-
url_adapter = _request_ctx_stack.top.url_adapter
121-
next = request.args.get('next')
122-
if next is not None:
123-
url = urlparse(next)
124-
if url.netloc != request.host:
125-
abort(400)
126-
endpoint, __ = url_adapter.match(url.path, 'GET')
127-
if endpoint != 'auth.sign_in_success':
128-
abort(400)
129-
query = parse_qs(url.query)
130-
next = query.get('next')
131-
if next is None:
132-
abort(400)
133-
url = urlparse(next[0])
134-
if url.netloc != request.host:
135-
abort(400)
136-
endpoint, values = url_adapter.match(url.path, 'GET')
137-
if endpoint != 'auth.site_authenticated':
138-
abort(400)
139-
site_id = values['site_id']
140-
site = synchronize(site_id)
141-
options = site.gitkit_options
142-
elif request.args.get('mode') == 'select':
143-
return render_template(
144-
'auth/close-window.html',
145-
message='You can only sign in to a specific site.')
146-
else:
147-
options = None
148-
else:
149-
options = None
150-
return render_template('auth/widget.html', options=options or {})
105+
return render_template('auth/close-window.html', message='You have signed in.')
106+
if request.args.get('mode') != 'select':
107+
return render_template('auth/widget.html', options={})
108+
url_adapter = _request_ctx_stack.top.url_adapter
109+
next = request.args.get('next')
110+
if next:
111+
url = urlparse(next)
112+
if url.netloc != request.host:
113+
abort(400)
114+
endpoint, values = url_adapter.match(url.path, 'GET')
115+
if endpoint != 'auth.sign_in_success':
116+
abort(400)
117+
site = synchronize(values['site_id'])
118+
return render_template('auth/widget.html', options=site.gitkit_options)
119+
url = urlparse(request.referrer)
120+
if url.netloc != request.host:
121+
abort(400)
122+
endpoint, __ = url_adapter.match(url.path, 'GET')
123+
if endpoint != request.endpoint:
124+
abort(400)
125+
oob_code = parse_qs(url.query).get('oobCode')
126+
if not oob_code or len(oob_code) != 1:
127+
abort(400)
128+
challenge = Challenge.query.get(oob_code[0])
129+
if challenge is None:
130+
abort(400)
131+
base_url = Sites(challenge.site_id).get_base_url()
132+
if not base_url.endswith('/'):
133+
base_url += '/'
134+
return redirect('{}#sign-in'.format(base_url))
151135

152136

153-
@blueprint.route('/sign-in-success')
154-
def sign_in_success():
137+
@blueprint.route('/site/<site_id>/sign-in-success')
138+
def sign_in_success(site_id):
155139
token = gitkit.verify_token()
156140
if token is None:
157141
abort(400)
@@ -160,21 +144,27 @@ def sign_in_success():
160144
if account is None:
161145
account = Account(id=token['id'])
162146
db.session.add(account)
163-
account.email = gitkit_account['email']
147+
email = gitkit_account['email']
148+
account.email = email
164149
account.email_verified = gitkit_account['email_verified']
165150
account.name = gitkit_account['name']
166151
account.photo_url = gitkit_account['photo_url']
167-
if not account.email_verified:
168-
if account.email_challenged is None:
169-
send_email_challenge(account)
170-
db.session.flush()
171-
response = redirect(url_for('.verify_email', id=account.id))
172-
db.session.commit()
173-
return response
174152
db.session.flush()
175-
login_user(account)
153+
if account.email_verified:
154+
login_user(account)
155+
db.session.commit()
156+
return render_template('auth/close-window.html', message='You have signed in.')
157+
oob_link = gitkit.get_email_verification_link(email)
158+
challenge = Challenge(
159+
oob_code=parse_qs(urlparse(oob_link).query)['oobCode'][0],
160+
site_id=site_id,
161+
account_id=account.id,
162+
moment=datetime.utcnow())
163+
db.session.add(challenge)
176164
db.session.commit()
177-
return redirect(request.args.get('next', request.url_root))
165+
text = render_template('auth/verify-email.txt', oob_link=oob_link)
166+
send(email, 'Verify email address', text)
167+
return render_template('auth/close-window.html', message='Email verification link sent.')
178168

179169

180170
@blueprint.route('/sign-out')
@@ -187,15 +177,6 @@ def sign_out():
187177
return response
188178

189179

190-
@blueprint.route('/account/<id>/verify-email', methods={'GET', 'POST'})
191-
def verify_email(id):
192-
account = Account.query.get_or_404(id)
193-
if request.method == 'POST' and not account.email_verified:
194-
send_email_challenge(account)
195-
db.session.commit()
196-
return render_template('auth/verify-email.html', account=account)
197-
198-
199180
@blueprint.route('/oob-action')
200181
def oob_action():
201182
result = gitkit.get_oob_result()
@@ -220,46 +201,30 @@ def oob_action():
220201
@jsonp
221202
def site_token(site_id):
222203
synchronize(site_id)
223-
if not current_user.is_authenticated:
224-
next = url_for('.site_authenticated', site_id=site_id, _external=True)
225-
location = auth_url_for('widget', mode='select', next=next, _external=True)
226-
return jsonify({
227-
'status_code': 401,
228-
'location': location,
229-
})
230-
for roles in current_user.roles:
231-
if roles.site_id == site_id:
232-
return jsonify({
233-
'status_code': 200,
234-
'access_token': token_serializer.dumps(current_user.id),
235-
'account': {
236-
'email': current_user.email,
237-
'name': current_user.name,
238-
'roles': roles.roles,
239-
},
240-
'sign_out': url_for('.sign_out', _external=True),
241-
})
242-
return jsonify({
243-
'status_code': 403,
244-
'account': {
245-
'email': current_user.email,
246-
'name': current_user.name
247-
},
204+
next = url_for('.sign_in_success', site_id=site_id, _external=True)
205+
response = {
206+
'sign_in': url_for('.widget', mode='select', next=next, _external=True),
248207
'sign_out': url_for('.sign_out', _external=True),
249-
})
250-
251-
252-
@blueprint.route('/site/<site_id>/authenticated')
253-
def site_authenticated(site_id):
254-
return render_template('auth/close-window.html', message='You have signed in.')
255-
256-
257-
def send_email_challenge(account):
258-
text = render_template(
259-
'auth/verify-email.txt',
260-
oob_link=gitkit.get_email_verification_link(account.email))
261-
send(account.email, 'Verify email address', text)
262-
account.email_challenged = datetime.utcnow()
208+
}
209+
user = current_user._get_current_object()
210+
if not user.is_authenticated:
211+
response['status_code'] = 401
212+
return jsonify(response)
213+
response['account'] = {
214+
'email': user.email,
215+
'email_verified': user.email_verified,
216+
'name': user.name,
217+
}
218+
for roles in user.roles:
219+
if roles.site_id == site_id:
220+
response['account']['roles'] = roles.roles
221+
break
222+
if not user.email_verified or 'roles' not in response['account']:
223+
response['status_code'] = 403
224+
return jsonify(response)
225+
response['status_code'] = 200
226+
response['access_token'] = token_serializer.dumps(user.id)
227+
return jsonify(response)
263228

264229

265230
def send(recipient, subject, text):

Diff for: app/jekylledit/controllers/site.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
import frontmatter
55

6-
from flask import json, jsonify, request, abort
6+
from flask import json, jsonify, request
77
from flask.ext.cors import cross_origin
88
from flask.ext.login import current_user, login_required
99
from flask.ext.principal import Permission
@@ -21,6 +21,7 @@ def commit(repository, filenames):
2121
with repository.transaction():
2222
repository.execute(['add'] + filenames)
2323
repository.execute([
24+
'-c', 'user.name=JekyllEdit',
2425
'-c', 'user.email={}'.format(current_user.email),
2526
'commit',
2627
'-m', 'File {} updated'.format(filenames[0]),

Diff for: app/jekylledit/migrations/versions/2cf8288266e0_.py

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
"""Track email challenges and their codes.
2+
3+
Revision ID: 2cf8288266e0
4+
Revises: b04e0f09ebca
5+
Create Date: 2016-05-26 10:41:15.385102
6+
7+
"""
8+
9+
# revision identifiers, used by Alembic.
10+
revision = '2cf8288266e0'
11+
down_revision = 'b04e0f09ebca'
12+
13+
from alembic import op
14+
import sqlalchemy as sa
15+
16+
17+
def upgrade():
18+
op.create_table(
19+
'challenge',
20+
sa.Column('oob_code', sa.Unicode(), nullable=False),
21+
sa.Column('site_id', sa.Unicode(), nullable=False),
22+
sa.Column('account_id', sa.Unicode(), nullable=False),
23+
sa.Column('moment', sa.DateTime(), nullable=False),
24+
sa.PrimaryKeyConstraint('oob_code', name=op.f('challenge_pkey')),
25+
sa.ForeignKeyConstraint(['site_id'], ['site.id'], name=op.f('challenge_site_id_fkey')),
26+
sa.ForeignKeyConstraint(['account_id'], ['account.id'], name=op.f('challenge_account_id_fkey')),
27+
)
28+
29+
30+
def downgrade():
31+
op.drop_table('challenge')

Diff for: app/jekylledit/model/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# flake8: noqa
22

33
from .base import db, migrate
4-
from .auth import Account, Roles, Site
4+
from .auth import Account, Challenge, Roles, Site
55
from .site import Repository, Sites

Diff for: app/jekylledit/model/auth.py

+9-4
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,20 @@ class Account(UserMixin, db.Model):
1919
id = db.Column(db.Unicode, primary_key=True)
2020
email = db.Column(db.Unicode, unique=True, nullable=False)
2121
email_verified = db.Column(db.Boolean, default=False, nullable=False)
22-
email_challenged = db.Column(db.DateTime)
2322
name = db.Column(db.Unicode)
2423
photo_url = db.Column(db.Unicode)
2524

2625
roles = db.relationship('Roles')
2726

28-
@property
29-
def is_active(self):
30-
return self.email_verified
27+
28+
class Challenge(db.Model):
29+
30+
__tablename__ = 'challenge'
31+
32+
oob_code = db.Column(db.Unicode, primary_key=True)
33+
site_id = db.Column(db.Unicode, db.ForeignKey('site.id'), nullable=False)
34+
account_id = db.Column(db.Unicode, db.ForeignKey('account.id'), nullable=False)
35+
moment = db.Column(db.DateTime, nullable=False)
3136

3237

3338
class Roles(db.Model):

Diff for: app/jekylledit/model/site.py

+15-8
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1-
import os.path, os
1+
import json
2+
import os
3+
import os.path
4+
import yaml
25
from base64 import b64decode
3-
46
import frontmatter
57

6-
from flask import json
7-
88
from contextlib import contextmanager
99
from subprocess import Popen, PIPE
1010

11+
import frontmatter
12+
1113

1214
class Repository:
1315

@@ -55,12 +57,12 @@ def __init__(self, name):
5557
self.name = name
5658
self.repository = Repository(name)
5759

58-
def get_config(self, filename = None):
60+
def get_config(self, filename=None):
5961
if filename is None:
6062
filename = 'jekylledit.json'
6163
with self.repository.open(filename, 'r') as fp:
6264
self.config = json.load(fp)
63-
if not 'languages' in self.config:
65+
if 'languages' not in self.config:
6466
self.config.update({'languages': ['en']})
6567
return self.config
6668

@@ -77,7 +79,7 @@ def get_drafts(self, category=None):
7779
drafts.append({
7880
'author': post.metadata['author'],
7981
'category': category,
80-
'date' : post.metadata['date'],
82+
'date': post.metadata['date'],
8183
'filename': os.path.join(category, f),
8284
'title': post.metadata['title']
8385
})
@@ -143,4 +145,9 @@ def save_media(self, media):
143145
with open(self.repository.path(filename), 'wb+') as fm:
144146
fm.write(b64decode(medio['data']))
145147
created.append(filename)
146-
return created
148+
return created
149+
150+
def get_base_url(self):
151+
with self.repository.open('_config.yaml', 'r') as fp:
152+
config = yaml.load(fp)
153+
return config['baseurl']

Diff for: app/jekylledit/templates/auth/change-email.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
You have requested to change the email address of your Jekyll Edit
1+
You have requested to change the email address of your JekyllEdit
22
account. Please confirm that you own the new address by clicking
33
on the verification link bellow.
44

Diff for: app/jekylledit/templates/auth/reset-password.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
You have requested to reset the password to your Jekyll Edit account.
1+
You have requested to reset the password to your JekyllEdit account.
22
Please click on the verification link below to complete the process.
33

44
Verification link: {{ oob_link }}

0 commit comments

Comments
 (0)