/* Copyright 2016-2017 Vector Creations Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package gomatrixserverlib

import (
	"encoding/json"
	"fmt"
	"strings"

	"golang.org/x/crypto/ed25519"

	"github.com/matrix-org/util"
)

const (
	join   = "join"
	ban    = "ban"
	leave  = "leave"
	invite = "invite"
	public = "public"
	// MRoomCreate https://matrix.org/docs/spec/client_server/r0.2.0.html#m-room-create
	MRoomCreate = "m.room.create"
	// MRoomJoinRules https://matrix.org/docs/spec/client_server/r0.2.0.html#m-room-join-rules
	MRoomJoinRules = "m.room.join_rules"
	// MRoomPowerLevels https://matrix.org/docs/spec/client_server/r0.2.0.html#m-room-power-levels
	MRoomPowerLevels = "m.room.power_levels"
	// MRoomMember https://matrix.org/docs/spec/client_server/r0.2.0.html#m-room-member
	MRoomMember = "m.room.member"
	// MRoomThirdPartyInvite https://matrix.org/docs/spec/client_server/r0.2.0.html#m-room-third-party-invite
	MRoomThirdPartyInvite = "m.room.third_party_invite"
	// MRoomAliases https://matrix.org/docs/spec/client_server/r0.2.0.html#m-room-aliases
	MRoomAliases = "m.room.aliases"
	// MRoomHistoryVisibility https://matrix.org/docs/spec/client_server/r0.2.0.html#m-room-history-visibility
	MRoomHistoryVisibility = "m.room.history_visibility"
	// MRoomRedaction https://matrix.org/docs/spec/client_server/r0.2.0.html#id21
	MRoomRedaction = "m.room.redaction"
)

// StateNeeded lists the event types and state_keys needed to authenticate an event.
type StateNeeded struct {
	// Is the m.room.create event needed to auth the event.
	Create bool
	// Is the m.room.join_rules event needed to auth the event.
	JoinRules bool
	// Is the m.room.power_levels event needed to auth the event.
	PowerLevels bool
	// List of m.room.member state_keys needed to auth the event
	Member []string
	// List of m.room.third_party_invite state_keys
	ThirdPartyInvite []string
}

// Tuples returns the needed state key tuples for performing auth on an event.
func (s StateNeeded) Tuples() (res []StateKeyTuple) {
	if s.Create {
		res = append(res, StateKeyTuple{MRoomCreate, ""})
	}
	if s.JoinRules {
		res = append(res, StateKeyTuple{MRoomJoinRules, ""})
	}
	if s.PowerLevels {
		res = append(res, StateKeyTuple{MRoomPowerLevels, ""})
	}
	for _, userID := range s.Member {
		res = append(res, StateKeyTuple{MRoomMember, userID})
	}
	for _, token := range s.ThirdPartyInvite {
		res = append(res, StateKeyTuple{MRoomThirdPartyInvite, token})
	}
	return
}

// AuthEventReferences returns the auth_events references for the StateNeeded. Returns an error if the
// provider returns an error. If an event is missing from the provider but is required in StateNeeded, it
// is skipped over: no error is returned.
func (s StateNeeded) AuthEventReferences(provider AuthEventProvider) (refs []EventReference, err error) { // nolint: gocyclo
	var e *Event
	if s.Create {
		if e, err = provider.Create(); err != nil {
			return
		} else if e != nil {
			refs = append(refs, e.EventReference())
		}
	}
	if s.JoinRules {
		if e, err = provider.JoinRules(); err != nil {
			return
		} else if e != nil {
			refs = append(refs, e.EventReference())
		}
	}
	if s.PowerLevels {
		if e, err = provider.PowerLevels(); err != nil {
			return
		} else if e != nil {
			refs = append(refs, e.EventReference())
		}
	}
	for _, userID := range s.Member {
		if e, err = provider.Member(userID); err != nil {
			return
		} else if e != nil {
			refs = append(refs, e.EventReference())
		}
	}
	for _, token := range s.ThirdPartyInvite {
		if e, err = provider.ThirdPartyInvite(token); err != nil {
			return
		} else if e != nil {
			refs = append(refs, e.EventReference())
		}
	}
	return
}

// StateNeededForEventBuilder returns the event types and state_keys needed to authenticate the
// event being built. These events should be put under 'auth_events' for the event being built.
// Returns an error if the state needed could not be calculated with the given builder, e.g
// if there is a m.room.member without a membership key.
func StateNeededForEventBuilder(builder *EventBuilder) (result StateNeeded, err error) {
	// Extract the 'content' object from the event if it is m.room.member as we need to know 'membership'
	var content *memberContent
	if builder.Type == MRoomMember {
		if err = json.Unmarshal(builder.Content, &content); err != nil {
			err = errorf("unparsable member event content: %s", err.Error())
			return
		}
	}
	err = accumulateStateNeeded(&result, builder.Type, builder.Sender, builder.StateKey, content)
	result.Member = util.UniqueStrings(result.Member)
	result.ThirdPartyInvite = util.UniqueStrings(result.ThirdPartyInvite)
	return
}

// StateNeededForAuth returns the event types and state_keys needed to authenticate an event.
// This takes a list of events to facilitate bulk processing when doing auth checks as part of state conflict resolution.
func StateNeededForAuth(events []Event) (result StateNeeded) {
	for _, event := range events {
		// Extract the 'content' object from the event if it is m.room.member as we need to know 'membership'
		var content *memberContent
		if event.Type() == MRoomMember {
			c, err := newMemberContentFromEvent(event)
			if err == nil {
				content = &c
			}
		}
		// Ignore errors when accumulating state needed.
		// The event will be rejected when the actual checks encounter the same error.
		_ = accumulateStateNeeded(&result, event.Type(), event.Sender(), event.StateKey(), content)
	}

	// Deduplicate the state keys.
	result.Member = util.UniqueStrings(result.Member)
	result.ThirdPartyInvite = util.UniqueStrings(result.ThirdPartyInvite)
	return
}

func accumulateStateNeeded(result *StateNeeded, eventType, sender string, stateKey *string, content *memberContent) (err error) {
	switch eventType {
	case MRoomCreate:
		// The create event doesn't require any state to authenticate.
		// https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L123
	case MRoomAliases:
		// Alias events need:
		//  * The create event.
		//    https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L128
		// Alias events need no further authentication.
		// https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L160
		result.Create = true
	case MRoomMember:
		// Member events need:
		//  * The previous membership of the target.
		//    https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L355
		//  * The current membership state of the sender.
		//    https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L348
		//  * The join rules for the room if the event is a join event.
		//    https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L361
		//  * The power levels for the room.
		//    https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L370
		//  * And optionally may require a m.third_party_invite event
		//    https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L393
		if content == nil {
			err = errorf("missing memberContent for m.room.member event")
			return
		}
		result.Create = true
		result.PowerLevels = true
		if stateKey != nil {
			result.Member = append(result.Member, sender, *stateKey)
		}
		if content.Membership == join {
			result.JoinRules = true
		}
		if content.ThirdPartyInvite != nil {
			token, tokErr := thirdPartyInviteToken(content.ThirdPartyInvite)
			if tokErr != nil {
				err = errorf("could not get third-party token: %s", tokErr)
				return
			}
			result.ThirdPartyInvite = append(result.ThirdPartyInvite, token)
		}

	default:
		// All other events need:
		//  * The membership of the sender.
		//    https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L177
		//  * The power levels for the room.
		//    https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L196
		result.Create = true
		result.PowerLevels = true
		result.Member = append(result.Member, sender)
	}
	return
}

// thirdPartyInviteToken extracts the token from the third_party_invite.
func thirdPartyInviteToken(thirdPartyInvite *memberThirdPartyInvite) (string, error) {
	if thirdPartyInvite.Signed.Token == "" {
		return "", fmt.Errorf("missing 'third_party_invite.signed.token' JSON key")
	}
	return thirdPartyInvite.Signed.Token, nil
}

// AuthEventProvider provides auth_events for the authentication checks.
type AuthEventProvider interface {
	// Create returns the m.room.create event for the room or nil if there isn't a m.room.create event.
	Create() (*Event, error)
	// JoinRules returns the m.room.join_rules event for the room or nil if there isn't a m.room.join_rules event.
	JoinRules() (*Event, error)
	// PowerLevels returns the m.room.power_levels event for the room or nil if there isn't a m.room.power_levels event.
	PowerLevels() (*Event, error)
	// Member returns the m.room.member event for the given user_id state_key or nil if there isn't a m.room.member event.
	Member(stateKey string) (*Event, error)
	// ThirdPartyInvite returns the m.room.third_party_invite event for the
	// given state_key or nil if there isn't a m.room.third_party_invite event
	ThirdPartyInvite(stateKey string) (*Event, error)
}

// AuthEvents is an implementation of AuthEventProvider backed by a map.
type AuthEvents struct {
	events map[StateKeyTuple]*Event
}

// AddEvent adds an event to the provider. If an event already existed for the (type, state_key) then
// the event is replaced with the new event. Only returns an error if the event is not a state event.
func (a *AuthEvents) AddEvent(event *Event) error {
	if event.StateKey() == nil {
		return fmt.Errorf("AddEvent: event %q does not have a state key", event.Type())
	}
	a.events[StateKeyTuple{event.Type(), *event.StateKey()}] = event
	return nil
}

// Create implements AuthEventProvider
func (a *AuthEvents) Create() (*Event, error) {
	return a.events[StateKeyTuple{MRoomCreate, ""}], nil
}

// JoinRules implements AuthEventProvider
func (a *AuthEvents) JoinRules() (*Event, error) {
	return a.events[StateKeyTuple{MRoomJoinRules, ""}], nil
}

// PowerLevels implements AuthEventProvider
func (a *AuthEvents) PowerLevels() (*Event, error) {
	return a.events[StateKeyTuple{MRoomPowerLevels, ""}], nil
}

// Member implements AuthEventProvider
func (a *AuthEvents) Member(stateKey string) (*Event, error) {
	return a.events[StateKeyTuple{MRoomMember, stateKey}], nil
}

// ThirdPartyInvite implements AuthEventProvider
func (a *AuthEvents) ThirdPartyInvite(stateKey string) (*Event, error) {
	return a.events[StateKeyTuple{MRoomThirdPartyInvite, stateKey}], nil
}

// NewAuthEvents returns an AuthEventProvider backed by the given events. New events can be added by
// calling AddEvent().
func NewAuthEvents(events []*Event) AuthEvents {
	a := AuthEvents{make(map[StateKeyTuple]*Event)}
	for _, e := range events {
		a.AddEvent(e) // nolint: errcheck
	}
	return a
}

// A NotAllowed error is returned if an event does not pass the auth checks.
type NotAllowed struct {
	Message string
}

func (a *NotAllowed) Error() string {
	return "eventauth: " + a.Message
}

func errorf(message string, args ...interface{}) error {
	return &NotAllowed{Message: fmt.Sprintf(message, args...)}
}

// Allowed checks whether an event is allowed by the auth events.
// It returns a NotAllowed error if the event is not allowed.
// If there was an error loading the auth events then it returns that error.
func Allowed(event Event, authEvents AuthEventProvider) error {
	switch event.Type() {
	case MRoomCreate:
		return createEventAllowed(event)
	case MRoomAliases:
		return aliasEventAllowed(event, authEvents)
	case MRoomMember:
		return memberEventAllowed(event, authEvents)
	case MRoomPowerLevels:
		return powerLevelsEventAllowed(event, authEvents)
	case MRoomRedaction:
		return redactEventAllowed(event, authEvents)
	default:
		return defaultEventAllowed(event, authEvents)
	}
}

// createEventAllowed checks whether the m.room.create event is allowed.
// It returns an error if the event is not allowed.
func createEventAllowed(event Event) error {
	if !event.StateKeyEquals("") {
		return errorf("create event state key is not empty: %v", event.StateKey())
	}
	roomIDDomain, err := domainFromID(event.RoomID())
	if err != nil {
		return err
	}
	senderDomain, err := domainFromID(event.Sender())
	if err != nil {
		return err
	}
	if senderDomain != roomIDDomain {
		return errorf("create event room ID domain does not match sender: %q != %q", roomIDDomain, senderDomain)
	}
	if len(event.PrevEvents()) > 0 {
		return errorf("create event must be the first event in the room: found %d prev_events", len(event.PrevEvents()))
	}
	return nil
}

// memberEventAllowed checks whether the m.room.member event is allowed.
// Membership events have different authentication rules to ordinary events.
func memberEventAllowed(event Event, authEvents AuthEventProvider) error {
	allower, err := newMembershipAllower(authEvents, event)
	if err != nil {
		return err
	}
	return allower.membershipAllowed(event)
}

// aliasEventAllowed checks whether the m.room.aliases event is allowed.
// Alias events have different authentication rules to ordinary events.
func aliasEventAllowed(event Event, authEvents AuthEventProvider) error {
	// The alias events have different auth rules to ordinary events.
	// In particular we allow any server to send a m.room.aliases event without checking if the sender is in the room.
	// This allows server admins to update the m.room.aliases event for their server when they change the aliases on their server.
	// https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L143-L160

	create, err := newCreateContentFromAuthEvents(authEvents)
	if err != nil {
		return err
	}

	senderDomain, err := domainFromID(event.Sender())
	if err != nil {
		return err
	}

	if event.RoomID() != create.roomID {
		return errorf("create event has different roomID: %q != %q", event.RoomID(), create.roomID)
	}

	// Check that server is allowed in the room by the m.room.federate flag.
	if err := create.domainAllowed(senderDomain); err != nil {
		return err
	}

	// Check that event is a state event.
	// Check that the state key matches the server sending this event.
	// https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L158
	if !event.StateKeyEquals(senderDomain) {
		return errorf("alias state_key does not match sender domain, %q != %q", senderDomain, event.StateKey())
	}

	return nil
}

// powerLevelsEventAllowed checks whether the m.room.power_levels event is allowed.
// It returns an error if the event is not allowed or if there was a problem
// loading the auth events needed.
func powerLevelsEventAllowed(event Event, authEvents AuthEventProvider) error {
	allower, err := newEventAllower(authEvents, event.Sender())
	if err != nil {
		return err
	}

	// power level events must pass the default checks.
	// These checks will catch if the user has a high enough level to set a m.room.power_levels state event.
	if err = allower.commonChecks(event); err != nil {
		return err
	}

	// Parse the power levels.
	newPowerLevels, err := newPowerLevelContentFromEvent(event)
	if err != nil {
		return err
	}

	// Check that the user levels are all valid user IDs
	// https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L1063
	for userID := range newPowerLevels.userLevels {
		if !isValidUserID(userID) {
			return errorf("Not a valid user ID: %q", userID)
		}
	}

	// Grab the old power level event so that we can check if the event existed.
	var oldEvent *Event
	if oldEvent, err = authEvents.PowerLevels(); err != nil {
		return err
	} else if oldEvent == nil {
		// If this is the first power level event then it can set the levels to
		// any value it wants to.
		// https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L1074
		return nil
	}

	// Grab the old levels so that we can compare new the levels against them.
	oldPowerLevels := allower.powerLevels
	senderLevel := oldPowerLevels.userLevel(event.Sender())

	// Check that the changes in event levels are allowed.
	if err = checkEventLevels(senderLevel, oldPowerLevels, newPowerLevels); err != nil {
		return err
	}

	// Check that the changes in user levels are allowed.
	return checkUserLevels(senderLevel, event.Sender(), oldPowerLevels, newPowerLevels)
}

// checkEventLevels checks that the changes in event levels are allowed.
func checkEventLevels(senderLevel int64, oldPowerLevels, newPowerLevels powerLevelContent) error {
	type levelPair struct {
		old int64
		new int64
	}
	// Build a list of event levels to check.
	// This differs slightly in behaviour from the code in synapse because it will use the
	// default value if a level is not present in one of the old or new events.

	// First add all the named levels.
	levelChecks := []levelPair{
		{oldPowerLevels.banLevel, newPowerLevels.banLevel},
		{oldPowerLevels.inviteLevel, newPowerLevels.inviteLevel},
		{oldPowerLevels.kickLevel, newPowerLevels.kickLevel},
		{oldPowerLevels.redactLevel, newPowerLevels.redactLevel},
		{oldPowerLevels.stateDefaultLevel, newPowerLevels.stateDefaultLevel},
		{oldPowerLevels.eventDefaultLevel, newPowerLevels.eventDefaultLevel},
	}

	// Then add checks for each event key in the new levels.
	// We use the default values for non-state events when applying the checks.
	// TODO: the per event levels do not distinguish between state and non-state events.
	// However the default values do make that distinction. We may want to change this.
	// For example if there is an entry for "my.custom.type" events it sets the level
	// for sending the event with and without a "state_key". But if there is no entry
	// for "my.custom.type it will use the state default when sent with a "state_key"
	// and will use the event default when sent without.
	const (
		isStateEvent = false
	)
	for eventType := range newPowerLevels.eventLevels {
		levelChecks = append(levelChecks, levelPair{
			oldPowerLevels.eventLevel(eventType, isStateEvent),
			newPowerLevels.eventLevel(eventType, isStateEvent),
		})
	}

	// Then add checks for each event key in the old levels.
	// Some of these will be duplicates of the ones added using the keys from
	// the new levels. But it doesn't hurt to run the checks twice for the same level.
	for eventType := range oldPowerLevels.eventLevels {
		levelChecks = append(levelChecks, levelPair{
			oldPowerLevels.eventLevel(eventType, isStateEvent),
			newPowerLevels.eventLevel(eventType, isStateEvent),
		})
	}

	// Check each of the levels in the list.
	for _, level := range levelChecks {
		// Check if the level is being changed.
		if level.old == level.new {
			// Levels are always allowed to stay the same.
			continue
		}

		// Users are allowed to change the level for an event if:
		//   * the old level was less than or equal to their own
		//   * the new level was less than or equal to their own
		// https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L1134

		// Check if the user is trying to set any of the levels to above their own.
		if senderLevel < level.new {
			return errorf(
				"sender with level %d is not allowed to change level from %d to %d"+
					" because the new level is above the level of the sender",
				senderLevel, level.old, level.new,
			)
		}

		// Check if the user is trying to set a level that was above their own.
		if senderLevel < level.old {
			return errorf(
				"sender with level %d is not allowed to change level from %d to %d"+
					" because the current level is above the level of the sender",
				senderLevel, level.old, level.new,
			)
		}
	}

	return nil
}

// checkUserLevels checks that the changes in user levels are allowed.
func checkUserLevels(senderLevel int64, senderID string, oldPowerLevels, newPowerLevels powerLevelContent) error {
	type levelPair struct {
		old    int64
		new    int64
		userID string
	}

	// Build a list of user levels to check.
	// This differs slightly in behaviour from the code in synapse because it will use the
	// default value if a level is not present in one of the old or new events.

	// First add the user default level.
	userLevelChecks := []levelPair{
		{oldPowerLevels.userDefaultLevel, newPowerLevels.userDefaultLevel, ""},
	}

	// Then add checks for each user key in the new levels.
	for userID := range newPowerLevels.userLevels {
		userLevelChecks = append(userLevelChecks, levelPair{
			oldPowerLevels.userLevel(userID), newPowerLevels.userLevel(userID), userID,
		})
	}

	// Then add checks for each user key in the old levels.
	// Some of these will be duplicates of the ones added using the keys from
	// the new levels. But it doesn't hurt to run the checks twice for the same level.
	for userID := range oldPowerLevels.userLevels {
		userLevelChecks = append(userLevelChecks, levelPair{
			oldPowerLevels.userLevel(userID), newPowerLevels.userLevel(userID), userID,
		})
	}

	// Check each of the levels in the list.
	for _, level := range userLevelChecks {
		// Check if the level is being changed.
		if level.old == level.new {
			// Levels are always allowed to stay the same.
			continue
		}

		// Users are allowed to change the level of other users if:
		//   * the old level was less than their own
		//   * the new level was less than or equal to their own
		// They are allowed to change their own level if:
		//   * the new level was less than or equal to their own
		// https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L1126-L1127
		// https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L1134

		// Check if the user is trying to set any of the levels to above their own.
		if senderLevel < level.new {
			return errorf(
				"sender with level %d is not allowed change user level from %d to %d"+
					" because the new level is above the level of the sender",
				senderLevel, level.old, level.new,
			)
		}

		// Check if the user is changing their own user level.
		if level.userID == senderID {
			// Users are always allowed to reduce their own user level.
			// We know that the user is reducing their level because of the previous checks.
			continue
		}

		// Check if the user is changing the level that was above or the same as their own.
		if senderLevel <= level.old {
			return errorf(
				"sender with level %d is not allowed to change user level from %d to %d"+
					" because the old level is equal to or above the level of the sender",
				senderLevel, level.old, level.new,
			)
		}
	}

	return nil
}

// redactEventAllowed checks whether the m.room.redaction event is allowed.
// It returns an error if the event is not allowed or if there was a problem
// loading the auth events needed.
func redactEventAllowed(event Event, authEvents AuthEventProvider) error {
	allower, err := newEventAllower(authEvents, event.Sender())
	if err != nil {
		return err
	}

	// redact events must pass the default checks,
	if err = allower.commonChecks(event); err != nil {
		return err
	}

	senderDomain, err := domainFromID(event.Sender())
	if err != nil {
		return err
	}

	redactDomain, err := domainFromID(event.Redacts())
	if err != nil {
		return err
	}

	// Servers are always allowed to redact their own messages.
	// This is so that users can redact their own messages, but since
	// we don't know which user ID sent the message being redacted
	// the only check we can do is to compare the domains of the
	// sender and the redacted event.
	// We leave it up to the sending server to implement the additional checks
	// to ensure that only events that should be redacted are redacted.
	if senderDomain == redactDomain {
		return nil
	}

	// Otherwise the sender must have enough power.
	// This allows room admins and ops to redact messages sent by other servers.
	senderLevel := allower.powerLevels.userLevel(event.Sender())
	redactLevel := allower.powerLevels.redactLevel
	if senderLevel >= redactLevel {
		return nil
	}

	return errorf(
		"%q is not allowed to redact message from %q. %d < %d",
		event.Sender(), redactDomain, senderLevel, redactLevel,
	)
}

// defaultEventAllowed checks whether the event is allowed by the default
// checks for events.
// It returns an error if the event is not allowed or if there was a
// problem loading the auth events needed.
func defaultEventAllowed(event Event, authEvents AuthEventProvider) error {
	allower, err := newEventAllower(authEvents, event.Sender())
	if err != nil {
		return err
	}

	return allower.commonChecks(event)
}

// An eventAllower has the information needed to authorise all events types
// other than m.room.create, m.room.member and m.room.aliases which are special.
type eventAllower struct {
	// The content of the m.room.create.
	create createContent
	// The content of the m.room.member event for the sender.
	member memberContent
	// The content of the m.room.power_levels event for the room.
	powerLevels powerLevelContent
}

// newEventAllower loads the information needed to authorise an event sent
// by a given user ID from the auth events.
func newEventAllower(authEvents AuthEventProvider, senderID string) (e eventAllower, err error) {
	if e.create, err = newCreateContentFromAuthEvents(authEvents); err != nil {
		return
	}
	if e.member, err = newMemberContentFromAuthEvents(authEvents, senderID); err != nil {
		return
	}
	if e.powerLevels, err = newPowerLevelContentFromAuthEvents(authEvents, e.create.Creator); err != nil {
		return
	}
	return
}

// commonChecks does the checks that are applied to all events types other than
// m.room.create, m.room.member, or m.room.alias.
func (e *eventAllower) commonChecks(event Event) error {
	if event.RoomID() != e.create.roomID {
		return errorf("create event has different roomID: %q != %q", event.RoomID(), e.create.roomID)
	}

	sender := event.Sender()
	stateKey := event.StateKey()

	if err := e.create.userIDAllowed(sender); err != nil {
		return err
	}

	// Check that the sender is in the room.
	// Every event other than m.room.create, m.room.member and m.room.aliases require this.
	if e.member.Membership != join {
		return errorf("sender %q not in room", sender)
	}

	senderLevel := e.powerLevels.userLevel(sender)
	eventLevel := e.powerLevels.eventLevel(event.Type(), stateKey != nil)
	if senderLevel < eventLevel {
		return errorf(
			"sender %q is not allowed to send event. %d < %d",
			event.Sender(), senderLevel, eventLevel,
		)
	}

	// Check that all state_keys that begin with '@' are only updated by users
	// with that ID.
	if stateKey != nil && len(*stateKey) > 0 && (*stateKey)[0] == '@' {
		if *stateKey != sender {
			return errorf(
				"sender %q is not allowed to modify the state belonging to %q",
				sender, *stateKey,
			)
		}
	}

	// TODO: Implement other restrictions on state_keys required by the specification.
	// However as synapse doesn't implement those checks at the moment we'll hold off
	// so that checks between the two codebases don't diverge too much.

	return nil
}

// A membershipAllower has the information needed to authenticate a m.room.member event
type membershipAllower struct {
	// The user ID of the user whose membership is changing.
	targetID string
	// The user ID of the user who sent the membership event.
	senderID string
	// The membership of the user who sent the membership event.
	senderMember memberContent
	// The previous membership of the user whose membership is changing.
	oldMember memberContent
	// The new membership of the user if this event is accepted.
	newMember memberContent
	// The m.room.create content for the room.
	create createContent
	// The m.room.power_levels content for the room.
	powerLevels powerLevelContent
	// The m.room.join_rules content for the room.
	joinRule joinRuleContent
	// The m.room.third_party_invite content referenced by this event.
	thirdPartyInvite thirdPartyInviteContent
}

// newMembershipAllower loads the information needed to authenticate the m.room.member event
// from the auth events.
func newMembershipAllower(authEvents AuthEventProvider, event Event) (m membershipAllower, err error) { // nolint: gocyclo
	stateKey := event.StateKey()
	if stateKey == nil {
		err = errorf("m.room.member must be a state event")
		return
	}
	// TODO: Check that the IDs are valid user IDs.
	m.targetID = *stateKey
	m.senderID = event.Sender()
	if m.create, err = newCreateContentFromAuthEvents(authEvents); err != nil {
		return
	}
	if m.newMember, err = newMemberContentFromEvent(event); err != nil {
		return
	}
	if m.oldMember, err = newMemberContentFromAuthEvents(authEvents, m.targetID); err != nil {
		return
	}
	if m.senderMember, err = newMemberContentFromAuthEvents(authEvents, m.senderID); err != nil {
		return
	}
	if m.powerLevels, err = newPowerLevelContentFromAuthEvents(authEvents, m.create.Creator); err != nil {
		return
	}
	// We only need to check the join rules if the proposed membership is "join".
	if m.newMember.Membership == "join" {
		if m.joinRule, err = newJoinRuleContentFromAuthEvents(authEvents); err != nil {
			return
		}
	}
	// If this event comes from a third_party_invite, we need to check it against the original event.
	if m.newMember.ThirdPartyInvite != nil {
		token := m.newMember.ThirdPartyInvite.Signed.Token
		if m.thirdPartyInvite, err = newThirdPartyInviteContentFromAuthEvents(authEvents, token); err != nil {
			return
		}
	}
	return
}

// membershipAllowed checks whether the membership event is allowed
func (m *membershipAllower) membershipAllowed(event Event) error { // nolint: gocyclo
	if m.create.roomID != event.RoomID() {
		return errorf("create event has different roomID: %q != %q", event.RoomID(), m.create.roomID)
	}
	if err := m.create.userIDAllowed(m.senderID); err != nil {
		return err
	}
	if err := m.create.userIDAllowed(m.targetID); err != nil {
		return err
	}
	// Special case the first join event in the room to allow the creator to join.
	// https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L328
	if m.targetID == m.create.Creator &&
		m.newMember.Membership == join &&
		m.senderID == m.targetID &&
		len(event.PrevEvents()) == 1 {

		// Grab the event ID of the previous event.
		prevEventID := event.PrevEvents()[0].EventID

		if prevEventID == m.create.eventID {
			// If this is the room creator joining the room directly after the
			// the create event, then allow.
			return nil
		}
		// Otherwise fall back to the normal checks.
	}

	if m.newMember.Membership == invite && m.newMember.ThirdPartyInvite != nil {
		// Special case third party invites
		// https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L393
		return m.membershipAllowedFromThirdPartyInvite()
	}

	if m.targetID == m.senderID {
		// If the state_key and the sender are the same then this is an attempt
		// by a user to update their own membership.
		return m.membershipAllowedSelf()
	}
	// Otherwise this is an attempt to modify the membership of somebody else.
	return m.membershipAllowedOther()
}

// membershipAllowedFronThirdPartyInvite determines if the member events is following
// up the third_party_invite event it claims.
func (m *membershipAllower) membershipAllowedFromThirdPartyInvite() error {
	// Check if the event's target matches with the Matrix ID provided by the
	// identity server.
	if m.targetID != m.newMember.ThirdPartyInvite.Signed.MXID {
		return errorf(
			"The invite target %s doesn't match with the Matrix ID provided by the identity server %s",
			m.targetID, m.newMember.ThirdPartyInvite.Signed.MXID,
		)
	}
	// Marshal the "signed" so it can be verified by VerifyJSON.
	marshalledSigned, err := json.Marshal(m.newMember.ThirdPartyInvite.Signed)
	if err != nil {
		return err
	}
	// Check each signature with each public key. If one signature could be
	// verified with one public key, accept the event.
	for _, publicKey := range m.thirdPartyInvite.PublicKeys {
		for domain, signatures := range m.newMember.ThirdPartyInvite.Signed.Signatures {
			for keyID := range signatures {
				if strings.HasPrefix(keyID, "ed25519") {
					if err = VerifyJSON(
						domain, KeyID(keyID),
						ed25519.PublicKey(publicKey.PublicKey),
						marshalledSigned,
					); err == nil {
						return nil
					}
				}
			}
		}
	}
	return errorf("Couldn't verify signature on third-party invite for %s", m.targetID)
}

// membershipAllowedSelf determines if the change made by the user to their own membership is allowed.
func (m *membershipAllower) membershipAllowedSelf() error { // nolint: gocyclo
	if m.newMember.Membership == join {
		// A user that is not in the room is allowed to join if the room
		// join rules are "public".
		if m.oldMember.Membership == leave && m.joinRule.JoinRule == public {
			return nil
		}
		// An invited user is allowed to join if the join rules are "public"
		if m.oldMember.Membership == invite && m.joinRule.JoinRule == public {
			return nil
		}
		// An invited user is allowed to join if the join rules are "invite"
		if m.oldMember.Membership == invite && m.joinRule.JoinRule == invite {
			return nil
		}
		// A joined user is allowed to update their join.
		if m.oldMember.Membership == join {
			return nil
		}
	}
	if m.newMember.Membership == leave {
		// A joined user is allowed to leave the room.
		if m.oldMember.Membership == join {
			return nil
		}
		// An invited user is allowed to reject an invite.
		if m.oldMember.Membership == invite {
			return nil
		}
	}
	return m.membershipFailed()
}

// membershipAllowedOther determines if the user is allowed to change the membership of another user.
func (m *membershipAllower) membershipAllowedOther() error { // nolint: gocyclo
	senderLevel := m.powerLevels.userLevel(m.senderID)
	targetLevel := m.powerLevels.userLevel(m.targetID)

	// You may only modify the membership of another user if you are in the room.
	if m.senderMember.Membership != join {
		return errorf("sender %q is not in the room", m.senderID)
	}

	if m.newMember.Membership == ban {
		// A user may ban another user if their level is high enough
		// https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L463
		if senderLevel >= m.powerLevels.banLevel &&
			senderLevel > targetLevel {
			return nil
		}
	}
	if m.newMember.Membership == leave {
		// A user may unban another user if their level is high enough.
		// This is doesn't require the same power_level checks as banning.
		// You can unban someone with higher power_level than you.
		// https://github.com/matrix-org/synapse/blob/v0.18.5/synapse/api/auth.py#L451
		if m.oldMember.Membership == ban && senderLevel >= m.powerLevels.banLevel {
			return nil
		}
		// A user may kick another user if their level is high enough.
		// TODO: You can kick a user that was already kicked, or has left the room, or was
		// never in the room in the first place. Do we want to allow these redundant kicks?
		if m.oldMember.Membership != ban &&
			senderLevel >= m.powerLevels.kickLevel &&
			senderLevel > targetLevel {
			return nil
		}
	}
	if m.newMember.Membership == invite {
		// A user may invite another user if the user has left the room.
		// and their level is high enough.
		if m.oldMember.Membership == leave && senderLevel >= m.powerLevels.inviteLevel {
			return nil
		}
		// A user may re-invite a user.
		if m.oldMember.Membership == invite && senderLevel >= m.powerLevels.inviteLevel {
			return nil
		}
	}

	return m.membershipFailed()
}

// membershipFailed returns a error explaining why the membership change was disallowed.
func (m *membershipAllower) membershipFailed() error {
	if m.senderID == m.targetID {
		return errorf(
			"%q is not allowed to change their membership from %q to %q",
			m.targetID, m.oldMember.Membership, m.newMember.Membership,
		)
	}

	return errorf(
		"%q is not allowed to change the membership of %q from %q to %q",
		m.senderID, m.targetID, m.oldMember.Membership, m.newMember.Membership,
	)
}
