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

Update AssignRole, RemoveRole and add new UpdateRole handlers #3672

Merged
merged 3 commits into from
Jun 20, 2024
Merged

Conversation

rdimitrov
Copy link
Member

Summary

The following PR update AssignRole, RemoveRole and add new UpdateRole handlers

Fixes #(related issue)

Change Type

Mark the type of change your PR introduces:

  • Bug fix (resolves an issue without affecting existing features)
  • Feature (adds new functionality without breaking changes)
  • Breaking change (may impact existing functionalities or require documentation updates)
  • Documentation (updates or additions to documentation)
  • Refactoring or test improvements (no bug fixes or new functionality)

Testing

Outline how the changes were tested, including steps to reproduce and any relevant configurations.
Attach screenshots if helpful.

Review Checklist:

  • Reviewed my own code for quality and clarity.
  • Added comments to complex or tricky code sections.
  • Updated any affected documentation.
  • Included tests that validate the fix or feature.
  • Checked that related changes are merged.

@rdimitrov rdimitrov requested a review from a team as a code owner June 19, 2024 16:29
@rdimitrov
Copy link
Member Author

Issue for creating unit tests for these handlers - #3673

@rdimitrov
Copy link
Member Author

Obviously don't merge as I haven't updated the unit tests but do review the code if you decide so 👍

Comment on lines +542 to +544
if errors.Is(err, sql.ErrNoRows) {
return nil, util.UserVisibleError(codes.NotFound, "no invitation found for this email, project and role")
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this be idempotent and just return 200 OK?

Copy link
Member Author

Choose a reason for hiding this comment

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

It is what we do for other handlers, such as - https://github.com/stacklok/minder/blob/d08b8f68f4a308c3c8c69f49fbdb81710596ddcb/internal/controlplane/handlers_profile.go#L95

Let's discuss if we should change this and we can do that in a follow-up for all similar use cases so it's consistent 👍

}

// Validate the subject and email - decide if it's an invitation or a role assignment
if sub == "" && email != "" && isEmail(email) {
Copy link
Contributor

Choose a reason for hiding this comment

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

if isEmail() returns false here I think we should return an internal error as it points to an internal data inconsistency already present in the database.

Copy link
Member Author

Choose a reason for hiding this comment

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

The email in this case is received as an argument that is part of the request message, so I think it makes sense to validate it and return codes.InvalidArgument if it doesn't look alright

} else {
// If we didn't get an error, this means there's an existing invite.
// We should update its expiration and send the response.
userInvite, err = s.store.UpdateInvitation(ctx, existingInvite.Code)
Copy link
Contributor

Choose a reason for hiding this comment

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

Here we should check the invite expiration date before determining if we should update it. We want to avoid replaying an invite in case someone gets access to an old email for 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.

Let me give some context. So the lifespan of the invites should be 7 days for expiration, but we'll keep them for 30 days before deleting them entirely so an admin can update them once they see they have expired. This bumps their expires time only so previous send links continue to work and this is the intended behavior.

This flow happens when an administrator invites someone else again and there was an existing invitation (meaning it is less than 30 days old, otherwise it should have been deleted).

The scenarios are:

  • If the invite expires <= 7 days old, they are fine and can accept.
  • If the invite is 7 days < expires < 30 days, the invite will not work unless the admin went and updated it through this flow in the last 7 days
  • If the invite is 30 < expires, it shouldn't be present in the db, that is for both the admin and the invitee.

In that sense, I don't think we need to check the expiration because this flow is called by the administrator explicitly and is about updating the expiration of an invite.

Comment on lines 607 to 611
// isEmail checks if the subject is an email address or not
func isEmail(subject string) bool {
// Define the regular expression for validating an email address
const emailRegexPattern = `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`
emailRegex := regexp.MustCompile(emailRegexPattern)
Copy link
Contributor

Choose a reason for hiding this comment

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

Compile the email regexp just once to reuse it across calls

Suggested change
// isEmail checks if the subject is an email address or not
func isEmail(subject string) bool {
// Define the regular expression for validating an email address
const emailRegexPattern = `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`
emailRegex := regexp.MustCompile(emailRegexPattern)
var emailRegexPattern *regexp.Regexp
// isEmail checks if the subject is an email address or not
func isEmail(subject string) bool {
// Define the regular expression for validating an email address
const emailRegexPattern = `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`
if emailRegexPattern == nil {
emailRegex = regexp.MustCompile(emailRegexPattern)
}

Copy link
Member Author

Choose a reason for hiding this comment

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

Good tip! Let me update this manually though as I think some of the env names in the suggestion are not correct.

Copy link
Contributor

Choose a reason for hiding this comment

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

@puerco the email regex was actually removed to let the e-mail sending service verify it, not minder (it's unlikely we'd ever get the regex right for all weird cases)

if a.Subject == identity.String() {
roleToDelete, err := authz.ParseRole(a.Role)
if err != nil {
return nil, util.UserVisibleError(codes.InvalidArgument, err.Error())
Copy link
Contributor

Choose a reason for hiding this comment

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

Just as in the other, if somehow an un parseable role is being returned by the client this points to an internal inconsistency, not an argument from the client

Suggested change
return nil, util.UserVisibleError(codes.InvalidArgument, err.Error())
return nil, util.UserVisibleError(codes.Internal, err.Error())

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks! I agree on this since the respond comes from calling openfga 👍

}

for _, a := range as {
if a.Subject == identity.String() {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this should also be checking for the role? We should match user + role before calling out that the assignment already exists

Copy link
Member Author

Choose a reason for hiding this comment

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

It was decided that a user can have only one role within a project so in this case this should be enough 👍

if errors.Is(err, sql.ErrNoRows) {
return nil, util.UserVisibleError(codes.InvalidArgument, "target project with ID %s not found", targetProject)
}
return nil, status.Errorf(codes.Internal, "error getting project: %v", err)
Copy link
Contributor

Choose a reason for hiding this comment

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

And here I think it is a case for InvalidArgument as it is querying using the input from the user :D

Suggested change
return nil, status.Errorf(codes.Internal, "error getting project: %v", err)
return nil, status.Errorf(codes.InvalidArgument, "error getting project: %v", err)

Copy link
Member Author

Choose a reason for hiding this comment

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

We have a deal on this one! 😃

Copy link
Contributor

Choose a reason for hiding this comment

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

but in this case it's some unexpected error, I think Internal is OK in this case? e.g. the database connection was lost and such

@rdimitrov rdimitrov force-pushed the roles branch 2 times, most recently from 84f4875 to 3ba9d50 Compare June 20, 2024 06:13
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
Signed-off-by: Radoslav Dimitrov <radoslav@stacklok.com>
@jhrozek
Copy link
Contributor

jhrozek commented Jun 20, 2024

@puerco not to roll over your review, but since the frontend team was waiting on the API to land, I did a review of the PR with @rdimitrov. If you feel like your comments need addressing let's bring them up as follow-up PRs

@rdimitrov
Copy link
Member Author

@jhrozek, @puerco - thanks for the reviews! 🙏

@rdimitrov rdimitrov merged commit 7a3bce6 into main Jun 20, 2024
20 checks passed
@rdimitrov rdimitrov deleted the roles branch June 20, 2024 08:04
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