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

Authorization/permissions for single domain's struct fields #53

Closed
frederikhors opened this issue May 10, 2022 · 2 comments
Closed

Authorization/permissions for single domain's struct fields #53

frederikhors opened this issue May 10, 2022 · 2 comments

Comments

@frederikhors
Copy link

Thanks for your work on this: it's great!

I have read https://threedots.tech/post/repository-secure-by-design, but I have a doubt about how to fix permissions for individual domain struct fields.

Let's say that for domain.Training struct if the logged in user is an attendee he cannot see the field notes (it's just an example).

In that amazing article you suggest to put the auth check on repository, using the domain's method CanUserSeeTraining(), great. I can use the same technique with that field only, but now this is a problem if in my repositories I use a code like:

func (r TrainingsFirestoreRepository) GetTraining(
 ctx context.Context,
 trainingUUID string,
 user training.User,
) (*training.Training, error) {
 firestoreTraining, err := r.trainingsCollection().Doc(trainingUUID).Get(ctx)

 if status.Code(err) == codes.NotFound {
  return nil, training.NotFoundError{trainingUUID}
 }
 if err != nil {
  return nil, errors.Wrap(err, "unable to get actual docs")
 }

 tr, err := r.unmarshalTraining(firestoreTraining)
 if err != nil {
  return nil, err
 }

 if err := training.CanUserSeeTrainingNotes(user, *tr); err != nil {
  // this is not beautiful this here because we already fetched data from DB that the user cannot see (notes)
  // return nil, err
 }

 return tr, nil
}

Plus now we have a problem when we call repository's methods like Relation("Training.Notes") maybe using relational DBs and tools like Gorm: we cannot control what fields are fetched in other repository's methods!

Another way I can think of this is to use the domain's method Notes() (which is also more "secure" as no one can forget to call that method now and it's used each time we unmarshal from DB, whatever repository's method is called):

func (user training.User, t Training) Notes() string {
  if training.CanUserSeeTrainingNotes {
    return t.notes
  }

  return ""
}

but I have doubts on this too.

Am I completely wrong to think this way?

What do you think?

@m110
Copy link
Member

m110 commented May 12, 2022

Hey @frederikhors!

The second solution also has the drawback of pulling all data from the database, so that doesn't fix it. :)

What I don't like about this approach is that Training suddenly needs to have some notion of the user executing the method. The proper syntax would be:

func (t Training) Notes(user training.User) string {

But not all requests in your system will have a user context. Some may be executed from Pub/Sub messages, some may come from an internal API or CLI tool. As a workaround, you will start manually crafting this "user" passed to the method.

we cannot control what fields are fetched in other repository's methods!

I think it's not an issue, as long as the protected data doesn't leave the repository. The only way around this would be to include your business logic in SQL queries, which doesn't sound great.

So yeah, I think I'd stick to doing this in the repository. Another way could be to expose two repository methods, like:

type repository interface {
    GetTrainingForTrainer(ctx context.Context, trainingUUID string) (*training.Training, error)
    GetTrainingForTrainee(ctx context.Context, trainingUUID string) (*training.Training, error)
}

Then your application layer could decide which one to call based on the user.

Or you can go super explicit and use two different structs, one without the notes field at all!

@frederikhors
Copy link
Author

Thank you very much.

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

No branches or pull requests

2 participants