jkomoros / boardgame

An in-progress framework in golang to easily build boardgame Progressive Web Apps
Apache License 2.0
31 stars 4 forks source link

Implement property ACLs #40

Closed jkomoros closed 7 years ago

jkomoros commented 7 years ago

Plus a way in the CLI to view the state as particular users.

jkomoros commented 7 years ago

To do this we need a notion of which player is which, which means we need to do #71 first.

jkomoros commented 7 years ago

Actually, as I explored #71 a bit more it turns out that the notion of User is totally separate from the base package. For now it's probably OK to just have a simple drop-down in game-view that tells the server which user I am for debug purposes and renders that state (this will still be useful for admin/debug most likely).

Later we'll update server to store cookies and interrogate them to determine which Player this particular user is for a given game.

jkomoros commented 7 years ago

We don't want individual packages to have to implement ACLS themselves, so we want a concept above State that prepares a state object for a given player. Maybe it just returns a json blob natively? This object will call marshalJSON on the given state, then deserialize it into an interface{}, giving it a map of strings. Then it will walk the deserialized object, interrogate the conversion rules defined on Delegate somehow, and modify the deserialized object based on the policies returned. Then it will re-JSON the result and return that.

Actually, it will marshal JSON separately for Game, and each Player. That allows us to not have to worry about sub-properties or anything.

//Is it weird that this is the only place the main library returns JSON?
//Are there any use cases where the sanitization should differ at different states in the game? I THINK those are covered in the playergrouping concept
//-1 returns an un-sanitized state
boardgame.GameManager.SerializedStateForPlayer(state State, playerIndex int) []byte, error

type propertyType int

const(
   TypeGame propertyType iota
   TypePlayer
   TypeDynamicComponent
)

type SanitizationPolicy int

//TODO: do I have to do anything special to support SanitizationGrouping concept from design doc?
const (
  SanitizationNoOp SanitizationPolicy = iota
  SanitizationLen
  SanitizationOrder
  SanitizationNull
)

boardgame.GameDelegate.SanitizationPolicyForProperty(container propertyType, name string) SanitizationPolicy
jkomoros commented 7 years ago
jkomoros commented 7 years ago

Reminder to figure out how Moves fit into all of this, too. There's some hidden state that needs to be captured into a Move.

... Note, it's only necessary if we will do client-side mutations of state via moves. Until that point it's not necessary.

jkomoros commented 7 years ago

Policies are always fixed. It's a struct with game, players keys that point to maps of strings and policy numbers. What can change is moving things to other containers that have a different policy, and group membership. Policies are a group and a visibility setting. Group 0 is self. Group 1 is all others. Groups beyond that are bespoke groups. Things not in the policy default to open. (there is no polciyopen because of that, only various degrees of closed )

Ah, also, which Group a user is in is also theoretically privileged information

jkomoros commented 7 years ago
GameDelegate StateSanitizationPolicy() StateSanitizationPolicy

type StateSanitizationPolicy struct {
  Game map[string]GroupSanitizationPolicy
  Players []map[string]GroupSanitizationPolicy
  Groups []GroupSanitizationPolicy
}

const (
  GroupSelf = -1
  GroupOther = -2
)

//To figure out which policy in a GroupSanitizationPolicy applies, we collect all of the policies that apply to the given player. Then we apply the *least* restrictive one.
type GroupSanitizationPolicy map[int]SanitizationPolicy

type SanitizationPolicy int

const (
  //Non sanitized
  PolicyVisible SanitizationPolicy= iota
  //For groups (e.g. stacks, int slices), return either 0 if empty or 1 if not empty
  PolicyNonEmpty
  //For groups (e.g. stacks, int slices), return the length
  PolicyLen
  //For groups (e.g. stacks, int slices), return a list of obfuscated but stable ids in place of the actual content
  PolicyOrder
  //Return the zero value for the property (e.g. False for bool, 0 for int, nil for stack) 
  PolicyHidden
)
jkomoros commented 7 years ago

Wait we don't need an array for players because same policy is applied to all players, right?

Is this the first time that we're enforcing that state for each player should be the same? Is that a thing we want to formally require?

jkomoros commented 7 years ago

We can probably pop out the generic group machinery until we fix #160, as long as the design is generic enough to work for it.

jkomoros commented 7 years ago
jkomoros commented 7 years ago

It might be nice if transformed properties were always the same "shape" as non transformed ones. That way they actually could be a real state object. I think all of the currently proposed policies could be implemented that way...

If all of the objects actually were honest-to-god State objects, that would make them easier to pass around and reason about. (As well as not having the awkwardness where we pass out json blobs).

In the future the AIs will be given sanitized states, and it would be nice if they really were just normal state objects.

jkomoros commented 7 years ago

Copy state and walk through config to rewrite all properties. Stacks might have their Len reduced to 0 or 1. ComonentAt with negative values below -1 will return a fake component, unique to the number and index (so comparisons work). -2 is used for ones higher sanitized than order (where all that is visible is len)