]> git.lizzy.rs Git - cheatdb.git/blob - app/blueprints/users/profile.py
Implement package states for easier reviews
[cheatdb.git] / app / blueprints / users / profile.py
1 # ContentDB
2 # Copyright (C) 2018  rubenwardy
3 #
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation, either version 3 of the License, or
7 # (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
17
18 from flask import *
19 from flask_user import signals, current_user, user_manager
20 from flask_login import login_user, logout_user
21 from app.markdown import render_markdown
22 from . import bp
23 from app.models import *
24 from flask_wtf import FlaskForm
25 from wtforms import *
26 from wtforms.validators import *
27 from app.utils import randomString, loginUser, rank_required, nonEmptyOrNone, addAuditLog
28 from app.tasks.forumtasks import checkForumAccount
29 from app.tasks.emails import sendVerifyEmail, sendEmailRaw
30 from app.tasks.phpbbparser import getProfile
31 from sqlalchemy import func
32
33 # Define the User profile form
34 class UserProfileForm(FlaskForm):
35         display_name = StringField("Display name", [Optional(), Length(2, 100)])
36         forums_username = StringField("Forums Username", [Optional(), Length(2, 50)])
37         github_username = StringField("GitHub Username", [Optional(), Length(2, 50)])
38         email = StringField("Email", [Optional(), Email()], filters = [lambda x: x or None])
39         website_url = StringField("Website URL", [Optional(), URL()], filters = [lambda x: x or None])
40         donate_url = StringField("Donation URL", [Optional(), URL()], filters = [lambda x: x or None])
41         rank = SelectField("Rank", [Optional()], choices=UserRank.choices(), coerce=UserRank.coerce, default=UserRank.NEW_MEMBER)
42         submit = SubmitField("Save")
43
44
45 @bp.route("/users/", methods=["GET"])
46 def list_all():
47         users = db.session.query(User, func.count(Package.id)) \
48                         .select_from(User).outerjoin(Package) \
49                         .order_by(db.desc(User.rank), db.asc(User.display_name)) \
50                         .group_by(User.id).all()
51
52         return render_template("users/list.html", users=users)
53
54
55 @bp.route("/users/<username>/", methods=["GET", "POST"])
56 def profile(username):
57         user = User.query.filter_by(username=username).first()
58         if not user:
59                 abort(404)
60
61         form = None
62         if user.checkPerm(current_user, Permission.CHANGE_USERNAMES) or \
63                         user.checkPerm(current_user, Permission.CHANGE_EMAIL) or \
64                         user.checkPerm(current_user, Permission.CHANGE_RANK):
65                 # Initialize form
66                 form = UserProfileForm(formdata=request.form, obj=user)
67
68                 # Process valid POST
69                 if request.method=="POST" and form.validate():
70                         severity = AuditSeverity.NORMAL if current_user == user else AuditSeverity.MODERATION
71                         addAuditLog(severity, current_user, "Edited {}'s profile".format(user.display_name),
72                                         url_for("users.profile", username=username))
73
74                         # Copy form fields to user_profile fields
75                         if user.checkPerm(current_user, Permission.CHANGE_USERNAMES):
76                                 user.display_name = form.display_name.data
77                                 user.forums_username = nonEmptyOrNone(form.forums_username.data)
78                                 user.github_username = nonEmptyOrNone(form.github_username.data)
79
80                         if user.checkPerm(current_user, Permission.CHANGE_PROFILE_URLS):
81                                 user.website_url  = form["website_url"].data
82                                 user.donate_url   = form["donate_url"].data
83
84                         if user.checkPerm(current_user, Permission.CHANGE_RANK):
85                                 newRank = form["rank"].data
86                                 if current_user.rank.atLeast(newRank):
87                                         if newRank != user.rank:
88                                                 user.rank = form["rank"].data
89                                                 msg = "Set rank of {} to {}".format(user.display_name, user.rank.getTitle())
90                                                 addAuditLog(AuditSeverity.MODERATION, current_user, msg, url_for("users.profile", username=username))
91                                 else:
92                                         flash("Can't promote a user to a rank higher than yourself!", "danger")
93
94                         if user.checkPerm(current_user, Permission.CHANGE_EMAIL):
95                                 newEmail = form["email"].data
96                                 if newEmail != user.email and newEmail.strip() != "":
97                                         token = randomString(32)
98
99                                         msg = "Changed email of {}".format(user.display_name)
100                                         addAuditLog(severity, current_user, msg, url_for("users.profile", username=username))
101
102                                         ver = UserEmailVerification()
103                                         ver.user  = user
104                                         ver.token = token
105                                         ver.email = newEmail
106                                         db.session.add(ver)
107                                         db.session.commit()
108
109                                         task = sendVerifyEmail.delay(newEmail, token)
110                                         return redirect(url_for("tasks.check", id=task.id, r=url_for("users.profile", username=username)))
111
112                         # Save user_profile
113                         db.session.commit()
114
115                         # Redirect to home page
116                         return redirect(url_for("users.profile", username=username))
117
118         packages = user.packages.filter(Package.state!=PackageState.DELETED)
119         if not current_user.is_authenticated or (user != current_user and not current_user.canAccessTodoList()):
120                 packages = packages.filter_by(state=PackageState.APPROVED)
121         packages = packages.order_by(db.asc(Package.title))
122
123         topics_to_add = None
124         if current_user == user or user.checkPerm(current_user, Permission.CHANGE_AUTHOR):
125                 topics_to_add = ForumTopic.query \
126                                         .filter_by(author_id=user.id) \
127                                         .filter(~ db.exists().where(Package.forums==ForumTopic.topic_id)) \
128                                         .order_by(db.asc(ForumTopic.name), db.asc(ForumTopic.title)) \
129                                         .all()
130
131         # Process GET or invalid POST
132         return render_template("users/profile.html",
133                         user=user, form=form, packages=packages, topics_to_add=topics_to_add)
134
135
136 @bp.route("/users/<username>/check/", methods=["POST"])
137 @login_required
138 def user_check(username):
139         user = User.query.filter_by(username=username).first()
140         if user is None:
141                 abort(404)
142
143         if current_user != user and not current_user.rank.atLeast(UserRank.MODERATOR):
144                 abort(403)
145
146         if user.forums_username is None:
147                 abort(404)
148
149         task = checkForumAccount.delay(user.forums_username)
150         next_url = url_for("users.profile", username=username)
151
152         return redirect(url_for("tasks.check", id=task.id, r=next_url))
153
154
155 class SendEmailForm(FlaskForm):
156         subject = StringField("Subject", [InputRequired(), Length(1, 300)])
157         text    = TextAreaField("Message", [InputRequired()])
158         submit  = SubmitField("Send")
159
160
161 @bp.route("/users/<username>/email/", methods=["GET", "POST"])
162 @rank_required(UserRank.MODERATOR)
163 def send_email(username):
164         user = User.query.filter_by(username=username).first()
165         if user is None:
166                 abort(404)
167
168         next_url = url_for("users.profile", username=user.username)
169
170         if user.email is None:
171                 flash("User has no email address!", "danger")
172                 return redirect(next_url)
173
174         form = SendEmailForm(request.form)
175         if form.validate_on_submit():
176                 addAuditLog(AuditSeverity.MODERATION, current_user,
177                                 "Sent email to {}".format(user.display_name), url_for("users.profile", username=username))
178
179                 text = form.text.data
180                 html = render_markdown(text)
181                 task = sendEmailRaw.delay([user.email], form.subject.data, text, html)
182                 return redirect(url_for("tasks.check", id=task.id, r=next_url))
183
184         return render_template("users/send_email.html", form=form)
185
186
187
188 class SetPasswordForm(FlaskForm):
189         email = StringField("Email", [Optional(), Email()])
190         password = PasswordField("New password", [InputRequired(), Length(2, 100)])
191         password2 = PasswordField("Verify password", [InputRequired(), Length(2, 100)])
192         submit = SubmitField("Save")
193
194 @bp.route("/user/set-password/", methods=["GET", "POST"])
195 @login_required
196 def set_password():
197         if current_user.hasPassword():
198                 return redirect(url_for("user.change_password"))
199
200         form = SetPasswordForm(request.form)
201         if current_user.email == None:
202                 form.email.validators = [InputRequired(), Email()]
203
204         if request.method == "POST" and form.validate():
205                 one = form.password.data
206                 two = form.password2.data
207                 if one == two:
208                         # Hash password
209                         hashed_password = user_manager.hash_password(form.password.data)
210
211                         # Change password
212                         current_user.password = hashed_password
213                         db.session.commit()
214
215                         # Send 'password_changed' email
216                         if user_manager.USER_ENABLE_EMAIL and current_user.email:
217                                 user_manager.email_manager.send_password_changed_email(current_user)
218
219                         # Send password_changed signal
220                         signals.user_changed_password.send(current_app._get_current_object(), user=current_user)
221
222                         # Prepare one-time system message
223                         flash('Your password has been changed successfully.', 'success')
224
225                         newEmail = form["email"].data
226                         if newEmail != current_user.email and newEmail.strip() != "":
227                                 token = randomString(32)
228
229                                 ver = UserEmailVerification()
230                                 ver.user = current_user
231                                 ver.token = token
232                                 ver.email = newEmail
233                                 db.session.add(ver)
234                                 db.session.commit()
235
236                                 task = sendVerifyEmail.delay(newEmail, token)
237                                 return redirect(url_for("tasks.check", id=task.id, r=url_for("users.profile", username=current_user.username)))
238                         else:
239                                 return redirect(url_for("user.login"))
240                 else:
241                         flash("Passwords do not match", "danger")
242
243         return render_template("users/set_password.html", form=form, optional=request.args.get("optional"))
244
245
246 @bp.route("/users/verify/")
247 def verify_email():
248         token = request.args.get("token")
249         ver = UserEmailVerification.query.filter_by(token=token).first()
250         if ver is None:
251                 flash("Unknown verification token!", "danger")
252         else:
253                 ver.user.email = ver.email
254                 db.session.delete(ver)
255                 db.session.commit()
256
257         if current_user.is_authenticated:
258                 return redirect(url_for("users.profile", username=current_user.username))
259         else:
260                 return redirect(url_for("homepage.home"))