Skip to content

Commit 0b67927

Browse files
committed
upgraded authentication, base modules etc..
1 parent 8d7845f commit 0b67927

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+912
-19207
lines changed

.hgignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,5 @@ urls.py
1212
templates
1313
static
1414
home
15+
migrations
16+

README.md

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,20 @@ Atuin is Scalebox's Flask web application skeleton
1010
- Flask-SQLAlchemy (pip install flask-sqlalchemy)
1111
- flask-cache (pip install flask-cache)
1212
- flask-babel (pip install flask-babel)
13+
- flask-migrate (pip install flask-migrate)
1314
- requests (pip install requests)
15+
- blinker (pip install blinker)
16+
- passlib (pip install passlib)
17+
- markdown (pip install markdown) (used for html-formatting functionalities)
1418

1519
Install all:
1620

1721
apt-get install python-dev
18-
pip install -U flask flask-login sqlalchemy flask-sqlalchemy flask-cache flask-babel requests
22+
pip install -U flask flask-login sqlalchemy flask-sqlalchemy flask-cache flask-babel flask-migrate requests markdown passlib blinker
1923

2024
## Launch devserver
2125

22-
./dev.py
26+
./dev.py runserver
2327

2428
or, for external use
2529

@@ -72,7 +76,7 @@ Installation
7276
7377
location /static/bootstrap {
7478
alias /home/project/prj/static/bootstrap/;
75-
expires 24h;
79+
expires 24h;
7680
}
7781
7882
location / {
@@ -87,7 +91,7 @@ Installation
8791

8892
- activate it
8993

90-
ln -s _/etc/nginx/sites-available/project_ _/etc/nginx/sites-enabled/project_
94+
ln -s /etc/nginx/sites-available/project /etc/nginx/sites-enabled/project
9195

9296

9397
# Other tools
@@ -100,8 +104,22 @@ with `auth.models` preloaded
100104

101105
./shell.py auth
102106

103-
## DB reset:
107+
## DB Manage and migrations
104108

105-
./initdb.py
106-
109+
FlaskAtuin uses Flask-migrate
110+
111+
### First db init (used only when project is **started**)
112+
113+
./initdb.py db init
114+
115+
### Upgrade to latest migrations
116+
117+
./initdb.py db upgrade
118+
119+
### To generate new migration
120+
121+
./initdb.py db migrate -m "description"
122+
123+
*check the files* and then **apply**
107124

125+
./initdb.py db upgrade

auth/admin.py

Lines changed: 102 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ def users_index():
2020

2121

2222
@bp.route("/users/<int:userid>")
23+
@bp.route("/users/<int:userid>/<links>")
2324
@login_required
24-
def users_get(userid):
25+
def users_get(userid, links=False):
2526
user = User.query.get_or_404(userid)
2627
user_d = {
2728
'id': user.id,
@@ -30,15 +31,96 @@ def users_get(userid):
3031
'name': user.name,
3132
'notes': user.notes,
3233
'role': user.role,
34+
35+
'activeuntil': user.active_until.isoformat() if user.active_until else None,
36+
'birthday': user.birthday.isoformat() if user.birthday else None,
37+
'gender': user.gender,
38+
39+
'url_links': url_for('.users_get', userid=user.id, links='links'),
40+
'url_visibility': url_for('.users_get_visibility', userid=user.id),
3341
}
3442
if user.last_login:
3543
user_d['last_login'] = user.last_login.ctime()
3644
else:
3745
user_d['last_login'] = None
3846

47+
if links:
48+
user_d['linked_users'] = []
49+
for lu in user.linked_users:
50+
user_d['linked_users'].append(
51+
{
52+
'id': lu.id,
53+
'username': lu.username,
54+
'name': lu.name,
55+
'linked_users_num': len(lu.get_linked_users())
56+
}
57+
)
58+
3959
return jsonify(user_d)
4060

4161

62+
@bp.route("/users/<int:userid>/visibility")
63+
@login_required
64+
def users_get_visibility(userid):
65+
user = User.query.get_or_404(userid)
66+
67+
r = {
68+
'viewedby': [],
69+
'views': []
70+
}
71+
72+
for pu in user.get_parents(10):
73+
r['viewedby'].append(
74+
{
75+
'id': pu.id,
76+
'username': pu.username,
77+
'name': pu.name,
78+
}
79+
)
80+
81+
for lu in user.get_linked_users(10):
82+
r['views'].append(
83+
{
84+
'id': lu.id,
85+
'username': lu.username,
86+
'name': lu.name,
87+
}
88+
)
89+
90+
return jsonify(r)
91+
92+
93+
@bp.route("/users/search")
94+
@login_required
95+
def users_search():
96+
q = request.args['q']
97+
if len(q) < 3:
98+
return 'QUERY_TOO_SHORT', 400
99+
100+
users = User.query.filter(User.name.like('%'+q+'%') | User.username.like('%'+q+'%')).limit(12).all()
101+
result = {
102+
'count': len(users),
103+
'results': [
104+
{
105+
'id': user.id,
106+
'usertype': user.usertype,
107+
'username': user.username,
108+
'name': user.name,
109+
'notes': user.notes,
110+
'role': user.role,
111+
112+
'activeuntil': user.active_until.isoformat() if user.active_until else None,
113+
'birthday': user.birthday.isoformat() if user.birthday else None,
114+
'gender': user.gender,
115+
116+
'linked_users_num': len(user.get_linked_users())
117+
} for user in users
118+
]
119+
}
120+
121+
return jsonify(**result)
122+
123+
42124
@bp.route("/users/save", methods=['POST'])
43125
@bp.route("/users/<int:userid>", methods=['POST'])
44126
@login_required
@@ -52,27 +134,44 @@ def users_save(userid=None):
52134
else:
53135
#new user
54136
user = User()
55-
137+
db.session.add(user)
138+
56139
user.usertype = request.form['usertype']
57140
user.username = request.form['username']
58141
user.name = request.form['name']
59142
user.notes = request.form['notes']
60143
user.role = request.form['role']
61144

145+
user.gender = request.form['gender']
146+
147+
if ('birthday' in request.form) and (request.form['birthday']):
148+
user.birthday = datetime.datetime.strptime(request.form['birthday'], '%d/%m/%Y')
149+
150+
if ('activeuntil' in request.form) and (request.form['activeuntil']):
151+
user.active_until = datetime.datetime.strptime(request.form['activeuntil'], '%d/%m/%Y')
152+
62153
if request.form['password'] != '':
63154
user.set_password(request.form['password'])
64155

65-
db.session.add(user)
66156
db.session.commit()
157+
# public ID based on generated ID
158+
if not user.public_id:
159+
user._generate_public_id()
160+
db.session.commit()
67161

68162
flash(u'User %s saved' % user.username)
69163
return redirect(url_for('auth.admin.users_index'))
70164

71165
@bp.route("/users/<int:userid>", methods=['DELETE'])
72166
@login_required
73167
def users_delete(userid):
168+
if (current_user.role != 'ADMIN'):
169+
return 'Nice try... :D', 403
170+
74171
user = User.query.get_or_404(userid)
172+
75173
db.session.delete(user)
76174
db.session.commit()
77175

78176
return 'OK'
177+

auth/models.py

Lines changed: 109 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,64 @@
11
import datetime
2+
import random
23
import hashlib
34
import collections
45

56
from passlib.hash import sha256_crypt
7+
from mailing import send_mail
8+
9+
from datastore import db, hybrid_property, backref
610

7-
from datastore import db
811

912
class User(db.Model):
1013
__tablename__ = 'users'
1114

1215
id = db.Column(db.Integer, primary_key=True)
16+
public_id = db.Column(db.Integer, index=True)
1317
usertype = db.Column(db.String)
1418
username = db.Column(db.String, unique=True, index=True)
1519
password = db.Column(db.String)
1620
name = db.Column(db.String)
1721
notes = db.Column(db.Text)
1822
role = db.Column(db.String, index=True)
1923

24+
birthday = db.Column(db.Date)
25+
gender = db.Column(db.String)
26+
27+
address_city = db.Column(db.String)
28+
address_zip = db.Column(db.String)
29+
address_country = db.Column(db.String)
30+
31+
otp = db.Column(db.String, default=None)
32+
otp_expire = db.Column(db.DateTime, default=None)
33+
34+
active_until = db.Column(db.DateTime, default=None, index=True)
35+
2036
ins_timestamp = db.Column(db.DateTime, default=datetime.datetime.now)
2137
upd_timestamp = db.Column(db.DateTime, default=datetime.datetime.now)
2238

2339
last_login = db.Column(db.DateTime, default=None)
2440

41+
policies = db.relationship('UserPolicy',
42+
lazy='joined')
43+
44+
__table_args__ = ( db.Index('ix_otp_expire_otp', 'otp_expire', 'otp'),
45+
)
46+
2547
usertypes_d = collections.OrderedDict([
2648
('staff', "Staff"),
2749
('customer', "Customer"),
2850
])
2951

3052
roles_d = collections.OrderedDict([
3153
('ADMIN', "Administrator"),
32-
('CUSTOMER', "Customer"),
54+
('AGENT', "Agent"),
55+
('USER', "User"),
3356
])
3457

3558
def __repr__(self):
3659
return "<User %s %s usertype=%s>" % (self.id, self.username, self.usertype)
3760

61+
3862
def get_id(self):
3963
return self.id
4064

@@ -64,6 +88,89 @@ def usertype_translated(self):
6488
def role_translated(self):
6589
return self.roles_d.get(self.role, self.role)
6690

91+
@property
92+
def age(self):
93+
try:
94+
age = (datetime.date.today() - self.birthday).days / 365.2425
95+
except:
96+
age = 0
97+
return int(age)
98+
99+
@classmethod
100+
def check_otp(cls, otp):
101+
user = cls.query.filter(cls.otp_expire > datetime.datetime.now(), cls.otp == otp).first()
102+
if user:
103+
#invalidate it
104+
user.otp_expire = datetime.datetime.now()
105+
106+
return user
107+
108+
def generate_otp(self):
109+
otp = hashlib.sha256(str(random.randint(99999, 999999))).hexdigest()
110+
self.otp = otp
111+
self.otp_expire = datetime.datetime.now() + datetime.timedelta(hours=24)
112+
113+
return otp
114+
115+
def send_email(self, subject, message):
116+
res = send_mail(subject, message, [
117+
{ 'email': self.username },
118+
])
119+
return res
120+
121+
def _generate_public_id(self):
122+
self.public_id = int( str(self.id) + str(random.randint(1000, 9999)) )
123+
124+
def has_function(self, func):
125+
if self.role == 'ADMIN':
126+
return True
127+
128+
if not self.policies:
129+
return False
130+
131+
if not hasattr(self, 'functions'):
132+
self.functions = self.policies[0].functions.split(',')
133+
134+
return (func in self.functions)
135+
136+
def as_dict(self, show=None, hide=None, recurse=None):
137+
""" Return a dictionary representation of this model.
138+
"""
139+
140+
obj_d = {
141+
'id' : self.id,
142+
'public_id' : self.public_id,
143+
'usertype' : self.usertype,
144+
'username' : self.username,
145+
'name' : self.name,
146+
'notes' : self.notes,
147+
'role' : self.role,
148+
149+
'birthday' : self.birthday.isoformat() if self.birthday else None,
150+
'gender' : self.gender,
151+
152+
'address_city' : self.address_city,
153+
'address_zip' : self.address_zip,
154+
'address_country' : self.address_country,
155+
156+
'active_until' : self.active_until.isoformat() if self.active_until else None,
157+
'last_login' : self.last_login.isoformat() if self.last_login else None,
158+
159+
'ins_timestamp' : self.ins_timestamp.isoformat() if self.ins_timestamp else None,
160+
'upd_timestamp' : self.upd_timestamp.isoformat() if self.upd_timestamp else None,
161+
}
162+
163+
return obj_d
164+
165+
166+
167+
class UserPolicy(db.Model):
168+
__tablename__ = 'policies'
169+
170+
role = db.Column(db.String, db.ForeignKey('users.role'), primary_key=True)
171+
functions = db.Column(db.Text)
172+
173+
67174
#class UserSession(db.Model):
68175
# id = db.Column(db.Integer, primary_key=True)
69176
# user_id = db.Column(db.Integer, db.ForeignKey('user.id'))

0 commit comments

Comments
 (0)