0

I am wondering which is the correct place to secure operations on entities based on caller's identity and privilages (roles) in an ORM based application exposed through REST API.

The example is written in simplified Spring/JPA style and in Kotlin. However the code serves here as an example to illustrate the questsion which I believe to be framework-agnostic in essence.

@Entity
class User {
   @PrimaryKey
   private var id: String = ""

   @OneToMany
   private val initiatedRelationships: MutableList<Relationship> = mutableListOf()

   @OneToMany
   private val receivedRelationships: MutableList<Relationship> = mutableListOf()

   // other members...
}

where Relationship represents a simple join-table between users and holds additional attribute - relationship status, eg. FRIEND, INVITATION, BLOCKED

@Entity
internal class UserRelationship {
    @EmbeddedId
    var UserRelationship? = null // composite key

    @ManyToOne
    val initiator: User? = null

    @ManyToOne
    val receiver: User? = null

    val status: String = ""  // FRIEND, INVITATION
}

I decided to contain some business logic in my Entity classes, so that Services do not need to handle with all the implemetnation details. That way, in Service layer I don't have to deal with UserRelationship table. I just ask User entity for receivedInvitations, sentInvitations, friends

@Entity
class User {
    // members described before ...
    val friends: List<User>
    get() {
        return initiatedRelationsips
        .filter { it.status == "FRIEND" }
        .map { it.receiver }
        .plus(
           receivedRelationships
           .filter { it.status == "FRIEND" }
           .map { it.initiator }
        )
    }
}

My question is what is the correct place to put the authorization logic - eg. the list of friends of given user can only by accessed by his friend or him directly.

  1. Would it by in Service layer (direct caller of User entity methods?)

    class UserService {
        // ...
        fun getFriendsOf(userId: UUID): List<User> {
            val user = userRepository.findById(userId)
            val principal = getPrincipal() // assume it gives you caller's identity
            if (user.id == principal.id) { 
                return friends // Authorization OK, caller has rights to user's view firends
            }
            if (user.friends.any { friend -> friend.id == principal.id }) {
                return friends // Authorization OK, caller has rights to user's view firends
            }
            throw UnauthorizedException() // Neither user himself nor his friend
        }
    }
    
  2. Or rather inside User entity, so that when an unauthorized caller (principal) tries to view friends (calls user.friends) an Business-Exception is thrown.

    @Entity
    class User {
       val friends: List<User>
       get() {
          if (/** Caller has no rights to read this field **/) {
             throw UnauthorizedException()
          }
          // here happy path, the caller is authorized
       }
    }
    

For now I found some resources on the topic:

PGliw
  • 221
  • 3
  • 7
  • 2
    usually there is no logic in domain layer so nothing inside of entity itself, therefore a service would be the right choice. That being said most frameworks have kind of their predefined ways of how they are supposed to be used, best to read documentation on Spring Security in this case (or get a book/udemy) – J Asgarov Jan 29 '22 at 19:07

0 Answers0