]> git.ipfire.org Git - ipfire.org.git/blob - src/web/users.py
users: Move passwd handler from people
[ipfire.org.git] / src / web / users.py
1 #!/usr/bin/python
2
3 import PIL
4 import imghdr
5 import io
6 import logging
7 import os.path
8 import tornado.web
9
10 from . import base
11 from . import ui_modules
12
13 COLOUR_LIGHT = (237,232,232)
14 COLOUR_DARK = (49,53,60)
15
16 class IndexHandler(base.BaseHandler):
17 @tornado.web.authenticated
18 def get(self):
19 results = None
20
21 # Query Term
22 q = self.get_argument("q", None)
23
24 # Peform search
25 if q:
26 results = self.backend.accounts.search(q)
27
28 self.render("users/index.html", q=q, results=results)
29
30
31 class ShowHandler(base.BaseHandler):
32 @tornado.web.authenticated
33 def get(self, uid):
34 account = self.backend.accounts.get_by_uid(uid)
35 if not account:
36 raise tornado.web.HTTPError(404, "Could not find account %s" % uid)
37
38 self.render("users/show.html", account=account)
39
40
41 class AvatarHandler(base.BaseHandler):
42 def get(self, uid):
43 # Get the desired size of the avatar file
44 size = self.get_argument("size", None)
45
46 try:
47 size = int(size)
48 except (TypeError, ValueError):
49 size = None
50
51 logging.debug("Querying for avatar of %s" % uid)
52
53 # Fetch user account
54 account = self.backend.accounts.get_by_uid(uid)
55 if not account:
56 raise tornado.web.HTTPError(404, "Could not find account %s" % uid)
57
58 # Allow downstream to cache this for a year
59 self.set_expires(31536000)
60
61 # Resize avatar
62 avatar = account.get_avatar(size)
63
64 # If there is no avatar, we serve a default image
65 if not avatar:
66 logging.debug("No avatar uploaded for %s" % account)
67
68 # Generate a random avatar with only one letter
69 avatar = self._get_avatar(account, size=size)
70
71 # Guess content type
72 type = imghdr.what(None, avatar)
73
74 # If we could not guess the type, we will try something else
75 if not type:
76 # Could this be an SVG file?
77 if avatar.startswith(b"<"):
78 type = "svg+xml"
79
80 # Set headers about content
81 self.set_header("Content-Disposition", "inline; filename=\"%s.%s\"" % (account.uid, type))
82 if type:
83 self.set_header("Content-Type", "image/%s" % type)
84
85 # Deliver payload
86 self.finish(avatar)
87
88 def _get_avatar(self, account, size=None, **args):
89 letter = ("%s" % account)[0].upper()
90
91 if size is None:
92 size = 256
93
94 # The generated avatar cannot be larger than 1024px
95 if size >= 2048:
96 size = 2048
97
98 # Cache key
99 key = "avatar:letter:%s:%s" % (letter, size)
100
101 # Fetch avatar from the cache
102 avatar = self.memcached.get(key)
103 if not avatar:
104 avatar = self._make_avatar(letter, size=size, **args)
105
106 # Cache for forever
107 self.memcached.set(key, avatar)
108
109 return avatar
110
111 def _make_avatar(self, letter, format="PNG", size=None, **args):
112 # Generate an image of the correct size
113 image = PIL.Image.new("RGBA", (size, size), COLOUR_LIGHT)
114
115 # Have a canvas
116 draw = PIL.ImageDraw.Draw(image)
117
118 # Load font
119 font = PIL.ImageFont.truetype(os.path.join(
120 self.application.settings.get("static_path", ""),
121 "fonts/Prompt-Bold.ttf"
122 ), size, encoding="unic")
123
124 # Determine size of the printed letter
125 w, h = font.getsize(letter)
126
127 # Mukta seems to be very broken and the height needs to be corrected
128 h //= 0.7
129
130 # Draw the letter in the center
131 draw.text(((size - w) / 2, (size - h) / 2), letter,
132 font=font, fill=COLOUR_DARK)
133
134 with io.BytesIO() as f:
135 # If writing out the image does not work with optimization,
136 # we try to write it out without any optimization.
137 try:
138 image.save(f, format, optimize=True, **args)
139 except:
140 image.save(f, format, **args)
141
142 return f.getvalue()
143
144
145 class PasswdHandler(base.BaseHandler):
146 @tornado.web.authenticated
147 def get(self, uid):
148 account = self.backend.accounts.get_by_uid(uid)
149 if not account:
150 raise tornado.web.HTTPError(404, "Could not find account %s" % uid)
151
152 # Check for permissions
153 if not account.can_be_managed_by(self.current_user):
154 raise tornado.web.HTTPError(403, "%s cannot manage %s" % (self.current_user, account))
155
156 self.render("users/passwd.html", account=account)
157
158 @tornado.web.authenticated
159 def post(self, uid):
160 account = self.backend.accounts.get_by_uid(uid)
161 if not account:
162 raise tornado.web.HTTPError(404, "Could not find account %s" % uid)
163
164 # Check for permissions
165 if not account.can_be_managed_by(self.current_user):
166 raise tornado.web.HTTPError(403, "%s cannot manage %s" % (self.current_user, account))
167
168 # Get current password
169 password = self.get_argument("password")
170
171 # Get new password
172 password1 = self.get_argument("password1")
173 password2 = self.get_argument("password2")
174
175 # Passwords must match
176 if not password1 == password2:
177 raise tornado.web.HTTPError(400, "Passwords do not match")
178
179 # XXX Check password complexity
180
181 # Check if old password matches
182 if not account.check_password(password):
183 raise tornado.web.HTTPError(403, "Incorrect password for %s" % account)
184
185 # Save new password
186 account.passwd(password1)
187
188 # Redirect back to user's page
189 self.redirect("/users/%s" % account.uid)
190
191
192 class GroupIndexHandler(base.BaseHandler):
193 @tornado.web.authenticated
194 def get(self):
195 # Only staff can see other groups
196 if not self.current_user.is_staff():
197 raise tornado.web.HTTPError(403)
198
199 self.render("users/groups/index.html")
200
201
202 class GroupShowHandler(base.BaseHandler):
203 @tornado.web.authenticated
204 def get(self, gid):
205 # Only staff can see other groups
206 if not self.current_user.is_staff():
207 raise tornado.web.HTTPError(403)
208
209 # Fetch group
210 group = self.backend.groups.get_by_gid(gid)
211 if not group:
212 raise tornado.web.HTTPError(404, "Could not find group %s" % gid)
213
214 self.render("users/groups/show.html", group=group)
215
216
217 class ListModule(ui_modules.UIModule):
218 def render(self, accounts, show_created_at=False):
219 return self.render_string("users/modules/list.html", accounts=accounts,
220 show_created_at=show_created_at)