-
Notifications
You must be signed in to change notification settings - Fork 87
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(printing): add rate limit to print job requests (closes tjcsl#662) #1666
base: dev
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi Aarush, thanks for the PR! Nice implementation with SImpleRateThrottle. I have a couple of suggestions.
- A rate of 10/minute is probably too lenient, maybe try something more along the lines of a request every 5 minutes?
- The current implementation ratelimits even if the request is not successful, i.e. the form is invalid. If a user tries to print and accidentally selects the wrong options (without an actual print job requested), they shouldn't have to wait for the ratelimit to expire.
Let us know if you need any help with this!
I think 2 requests / 5 minutes is a good ratelimit for successful POST requests. I don't think turning the printing view into an API view is a good idea. It doesn't give a good error screen when the requests are throttled. Instead, the user should be presented with a good error screen, perhaps using the messages module we already have, and told they cannot print more than twice every 5 minutes. Perhaps you will find another package to accomplish this, but keep in mind only successful POST requests should be tracked. Otherwise, you may need to write your own backend and model for this. |
9366b9e
to
0287f01
Compare
I tried to use django-ratelimit but decided that the package is just not meant to do what I was looking for and decided to use caching to make a sort of DIY ratelimit. I implemented the django cache system into two functions that I check for directly in the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is good! Can you make the ratelimit a setting variable, instead of directly hardcoded? For example,
# 2 print jobs every 5 minutes
PRINT_RATELIMIT_FREQUENCY = 2
PRINT_RATELIMIT_MINUTES = 5
would control the ratelimit behavior.
4cef985
to
8cef1cc
Compare
Done 👍 |
This is great! Last few comments:
|
Everything implemented except for point 2 (partially). I use |
That's fine. Good call. Almost ready to merge, I'm assuming you'll fix CI, last thing it looks like "WizardoWaldo" (your alt?) is included in the commit. I don't really care about that but if you do you might want to take that off. |
Yea, sorry about that I'm trying to fix it. Pushing using the PyCharm gui did that for some reason |
8e42e7a
to
2ce3d7d
Compare
I'm not sure why the linting is failing. It seems like it has to do with modules I never edited? |
6982c00
to
bebf1df
Compare
@@ -23,6 +23,7 @@ | |||
<b>The following restrictions apply:</b> | |||
<ul> | |||
<li>Print jobs can have a maximum of {{ DJANGO_SETTINGS.PRINTING_PAGES_LIMIT }} pages.</li> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You need to change this to reflect the new teacher/student settings.
@@ -23,6 +23,7 @@ | |||
<b>The following restrictions apply:</b> | |||
<ul> | |||
<li>Print jobs can have a maximum of {{ DJANGO_SETTINGS.PRINTING_PAGES_LIMIT }} pages.</li> | |||
<li>You can send {{ DJANGO_SETTINGS.PRINT_RATELIMIT_FREQUENCY}} print jobs every {{ DJANGO_SETTINGS.PRINT_RATELIMIT_MINUTES }} minutes.</li> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Small reword - You may send up to XX print jobs every XX minutes.
intranet/apps/printing/views.py
Outdated
if get_user_ratelimit_status(obj.user.username): | ||
# Bypass rate limit for admins but still send error message for debugging purposes | ||
if obj.user.has_admin_permission("printing"): | ||
logger.error( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logger.debug is more appropriate here
intranet/apps/printing/views.py
Outdated
@@ -349,17 +380,44 @@ def print_job(obj: PrintJob, do_print: bool = True): | |||
if obj.page_range: | |||
if not range_count: | |||
raise InvalidInputPrintingError("You specified an invalid page range.") | |||
elif range_count > settings.PRINTING_PAGES_LIMIT: | |||
elif range_count > settings.PRINTING_PAGES_LIMIT_STUDENTS and obj.user.is_student: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are user types other than students and teachers. Apply the teacher limit to teachers and counselors (and, while we're at it, if the user is an admin they can get the teacher limit too). Apply the student limit to everyone else. Same logic for ratelimiting. No ratelimit for teachers and counselors. Ratelimit for everyone else (except admins).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, Justin reminded me is_teacher
checks for teacher or counselor. So that's ok. But you will still need to cover other user types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just made the first if statement check for printing admin perms or if they are a teacher, and anyone else gets the student limit because of the order the elifs appear in
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very very close. Sorry for all the comments about small details.
Can you do a favor for me? user.has_print_permission
is a weird property. All the other admin ones are named like user.is_printing_admin
. Could you global replace has_print_permission
with is_printing_admin
? This isn't strictly related to your PR but it'd be a good change long-term for Ion.
{% else %} | ||
<li>Print jobs can have a maximum of {{ DJANGO_SETTINGS.PRINTING_PAGES_LIMIT_STUDENTS }} pages.</li> | ||
{% endif %} | ||
{% if user.is_student %} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Small nitpick. Following convention this should be not user.is_teacher and not user.has_print_permission
No worries, hopefully it's all good now 👍 |
intranet/apps/printing/views.py
Outdated
settings.PRINT_RATELIMIT_MINUTES, | ||
) | ||
# If user needs to be rate limited | ||
elif obj.user.is_student: # Don't rate limit teachers |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This goes back to what we were discussing earlier - if printing admin or teacher, bypass, else apply ratelimit logic. Not just students.
Proposed changes
Brief description of rationale
Blocks excessive printing by users to prevent abuse or overload of the printers