Skip to content
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

refactor: modify REST API endpoints for LR MFE #1760

Merged
merged 1 commit into from
Sep 26, 2022
Merged

Conversation

MaxFrank13
Copy link
Member

@MaxFrank13 MaxFrank13 commented Sep 21, 2022

This is a PR to modify the existing REST API endpoints used by the LR MFE. This modification adds a username query param that is employed the the Support Tools MFE to fetch the same data learners see.

Custom permissions classes were necessary here to block access from unauthorized users. We needed to ensure that learners (or other non-staff, non-superusers) couldn't view the records of others.

Here is a truth table that attempts to illustrate how access is handled with the addition of this query parameter.

@MaxFrank13 MaxFrank13 force-pushed the mfrank/APER-1992 branch 4 times, most recently from ff92630 to 1b7da27 Compare September 22, 2022 16:56
Allows access if the requesting user is the owner of the record, a staff member, or a superuser.
"""

def has_permission(self, request, view):
Copy link
Contributor

@cdeery cdeery Sep 22, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this check if the requesting user is the owner of the record? I can see that it works in the unit tests, but I don't understand the mechanism.

Copy link
Member Author

@MaxFrank13 MaxFrank13 Sep 22, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh nice catch! It actually doesn't. I need to change the documentation. I initially thought we'd allow the user to do this but now I'm under the impression that a learner/normal user should never need to use this username query parameter.

Comment on lines 46 to 51
programs = get_user_program_data(
searched_user, request.site, include_empty_programs=False, include_retired_programs=True
)
else:
programs = get_user_program_data(
request.user.username, request.site, include_empty_programs=False, include_retired_programs=True
Copy link
Contributor

@justinhynes justinhynes Sep 22, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple of things...

  1. I think the information we're passing to get_user_program_data here differs in your if vs. your else. In the if, you are sending the entire User object to get_user_program_data. In your else, you are sending just the username. Which should it be?

  2. I think we want to code a bit more defensively. I think this code has a chance to throw an exception and maybe not return any data back to the caller. It's not guaranteed a user will exist with the username passed in to the API call. A call to {Model}.objects.get(blah=blah) will throw a DoesNotExist exception if the object doesn't exist. We will likely want to do something like...

username = request.user.username

query_param_username = request.query_params.get("username", "")
if query_param_username:
    try:
        other_user = User.objects.get(username=query_param_username)
    except User.DoesNotExist:
        # Log something here
        return Response({"enrolled_programs": []}) # empty list, string, dictionary, whatever we would pass back when there is no data
    else:
        (else section only executes if the "try" didn't throw an exception)
        username = other_user.username

programs = get_user_program_data(
    username, request.site, include_empty_programs=False, include_retired_programs=True
)

This way we also cut down on code duplication of the call to get_user_program_data.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had a feeling there may be a better way to do this. Thanks for the example!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An interesting note about the first question you had: It is supposed to be a username, however, it was still working by passing in the User. Is this some Django magic perhaps?

Copy link
Contributor

@justinhynes justinhynes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me know if you have any questions regarding my comments/suggestions!

Copy link
Contributor

@justinhynes justinhynes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had a few minor requests to update logs and comments but I don't need to re-review afterwards. Thanks, Max!

@MaxFrank13 MaxFrank13 force-pushed the mfrank/APER-1992 branch 2 times, most recently from 54679b9 to cad54de Compare September 26, 2022 12:44
@MaxFrank13 MaxFrank13 merged commit b0d8339 into master Sep 26, 2022
@MaxFrank13 MaxFrank13 deleted the mfrank/APER-1992 branch September 26, 2022 13:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants