func (c MembershipChains) GetRights(member *ttnpb.OrganizationOrUserIdentifiers, entityID ttnpb.IDStringer) *ttnpb.Rights {
	var entityRights *ttnpb.Rights
	for _, membership := range c {
		switch member.EntityType() {
		case "organization":
			if membership.OrganizationIdentifiers == nil || membership.OrganizationIdentifiers.IDString() != member.IDString() {
				continue
			}
		case "user":
			if membership.UserIdentifiers == nil || membership.UserIdentifiers.IDString() != member.IDString() {
				continue
			}
		default:
			continue
		}
		if membership.EntityIdentifiers.EntityType() != entityID.EntityType() || membership.EntityIdentifiers.IDString() != entityID.IDString() {
			continue
		}
		entityRights = entityRights.Union(membership.GetRights())
	}
	return entityRights
}
GetRights returns the rights of the member on the entity.
package store
import (
	"context"
	"fmt"
	"runtime/trace"
	"github.com/jinzhu/gorm"
	"go.thethings.network/lorawan-stack/v3/pkg/errors"
	"go.thethings.network/lorawan-stack/v3/pkg/ttnpb"
)
func GetMembershipStore(db *gorm.DB) MembershipStore {
	return &membershipStore{store: newStore(db)}
}
type membershipStore struct {
	*store
}
func (s *membershipStore) queryWithIndirectMemberships(ctx context.Context, entityType string, entityIDs ...string) *gorm.DB {
	idColumnName := fmt.Sprintf(`"%[1]ss"."%[1]s_id"`, entityType)
	if entityType == "organization" {
		idColumnName = `"organization_accounts"."uid"`
	}
	query := s.query(ctx, modelForEntityType(entityType)).Select([]string{
		`"indirect_accounts"."account_type" "indirect_account_type"`,
		`"indirect_accounts"."uid" "indirect_account_friendly_id"`,
		`"indirect_memberships"."rights" "indirect_account_rights"`,
		`"direct_accounts"."account_type" "direct_account_type"`,
		`"direct_accounts"."uid" "direct_account_friendly_id"`,
		`"direct_memberships"."rights" "direct_account_rights"`,
		`"direct_memberships"."entity_type" "entity_type"`,
		idColumnName + ` "entity_friendly_id"`,
	})
	if len(entityIDs) > 0 {
		query = query.Where(idColumnName+" IN (?)", entityIDs)
	}
	if entityType == "organization" {
		query = query.Joins(`JOIN "accounts" "organization_accounts" ON "organization_accounts"."account_id" = "organizations"."id" AND "organization_accounts"."account_type" = 'organization'`)
	}
	query = query.Joins(fmt.Sprintf(`JOIN "memberships" "direct_memberships" ON "direct_memberships"."entity_id" = "%[1]ss"."id" AND "direct_memberships"."entity_type" = '%[1]s'`, entityType)).
		Joins(`JOIN "accounts" "direct_accounts" ON "direct_accounts"."id" = "direct_memberships"."account_id"`).
		Joins(`LEFT JOIN "memberships" "indirect_memberships" ON "indirect_memberships"."entity_id" = "direct_accounts"."account_id" AND "indirect_memberships"."entity_type" = "direct_accounts"."account_type"`).
		Joins(`LEFT JOIN "accounts" "indirect_accounts" ON "indirect_accounts"."id" = "indirect_memberships"."account_id"`)
	query = query.Where(`"direct_accounts"."deleted_at" IS NULL AND "indirect_accounts"."deleted_at" IS NULL`)
	return query
}
func (s *membershipStore) queryWithDirectMemberships(ctx context.Context, entityType string, entityIDs ...string) *gorm.DB {
	idColumnName := fmt.Sprintf(`"%[1]ss"."%[1]s_id"`, entityType)
	if entityType == "organization" {
		idColumnName = `"organization_accounts"."uid"`
	}
	query := s.query(ctx, modelForEntityType(entityType)).Select([]string{
		`"direct_accounts"."account_type" "direct_account_type"`,
		`"direct_accounts"."uid" "direct_account_friendly_id"`,
		`"direct_memberships"."rights" "direct_account_rights"`,
		`"direct_memberships"."entity_type" "entity_type"`,
		idColumnName + ` "entity_friendly_id"`,
	})
	if len(entityIDs) > 0 {
		query = query.Where(idColumnName+" IN (?)", entityIDs)
	}
	if entityType == "organization" {
		query = query.Joins(`JOIN "accounts" "organization_accounts" ON "organization_accounts"."account_id" = "organizations"."id" AND "organization_accounts"."account_type" = 'organization'`)
	}
	query = query.Joins(fmt.Sprintf(`JOIN "memberships" "direct_memberships" ON "direct_memberships"."entity_id" = "%[1]ss"."id" AND "direct_memberships"."entity_type" = '%[1]s'`, entityType)).
		Joins(`JOIN "accounts" "direct_accounts" ON "direct_accounts"."id" = "direct_memberships"."account_id"`)
	query = query.Where(`"direct_accounts"."deleted_at" IS NULL`)
	return query
}
func (s *membershipStore) queryMemberships(ctx context.Context, accountID *ttnpb.OrganizationOrUserIdentifiers, entityType string, entityIDs []string, includeIndirect bool) *gorm.DB {
	if accountID.EntityType() == "user" && includeIndirect {
		return s.queryWithIndirectMemberships(ctx, entityType, entityIDs...).Where(
			`("direct_accounts"."account_type" = 'user' AND "direct_accounts"."uid" = ?) OR ("indirect_accounts"."account_type" = 'user' AND "indirect_accounts"."uid" = ?)`,
			accountID.IDString(), accountID.IDString(),
		)
	}
	return s.queryWithDirectMemberships(ctx, entityType, entityIDs...).Where(
		fmt.Sprintf(`"direct_accounts"."account_type" = '%s' AND "direct_accounts"."uid" = ?`, accountID.EntityType()),
		accountID.IDString(),
	)
}
func (s *membershipStore) FindMemberships(ctx context.Context, accountID *ttnpb.OrganizationOrUserIdentifiers, entityType string, includeIndirect bool) ([]*ttnpb.EntityIdentifiers, error) {
	defer trace.StartRegion(ctx, fmt.Sprintf("find %s memberships of %s", entityType, accountID.IDString())).End()
	membershipsQuery := s.queryMemberships(ctx, accountID, entityType, nil, includeIndirect).Select(`"direct_memberships"."entity_id"`).QueryExpr()
	query := s.query(ctx, modelForEntityType(entityType)).Where(fmt.Sprintf(`"%[1]ss"."id" IN (?)`, entityType), membershipsQuery)
	switch entityType {
	case "organization":
		query = query.
			Joins(`JOIN "accounts" ON "accounts"."account_type" = 'organization' AND "accounts"."account_id" = "organizations"."id"`).
			Select(`"accounts"."uid" AS "friendly_id"`)
	default:
		query = query.
			Select(fmt.Sprintf(`"%[1]ss"."%[1]s_id" AS "friendly_id"`, entityType))
	}
	query = query.Order(orderFromContext(ctx, fmt.Sprintf("%[1]ss", entityType), "friendly_id", "ASC"))
	page := query
	if limit, offset := limitAndOffsetFromContext(ctx); limit != 0 {
		page = query.Limit(limit).Offset(offset)
	}
	var results []struct {
		FriendlyID string
	}
	if err := page.Scan(&results).Error; err != nil {
		return nil, err
	}
	if limit, offset := limitAndOffsetFromContext(ctx); limit != 0 && (offset > 0 || len(results) == int(limit)) {
		countTotal(ctx, query)
	} else {
		setTotal(ctx, uint64(len(results)))
	}
	identifiers := make([]*ttnpb.EntityIdentifiers, len(results))
	for i, result := range results {
		identifiers[i] = buildIdentifiers(entityType, result.FriendlyID)
	}
	return identifiers, nil
}
type membershipChain struct {
	IndirectAccountType       string
	IndirectAccountFriendlyID string
	IndirectAccountRights     Rights
	DirectAccountType         string
	DirectAccountFriendlyID   string
	DirectAccountRights       Rights
	EntityType                string
	EntityFriendlyID          string
}
func (m membershipChain) GetMembershipChain() *MembershipChain {
	indirectAccountRights := ttnpb.Rights(m.IndirectAccountRights)
	directAccountRights := ttnpb.Rights(m.DirectAccountRights)
	c := &MembershipChain{
		RightsOnEntity:    &directAccountRights,
		EntityIdentifiers: buildIdentifiers(m.EntityType, m.EntityFriendlyID),
	}
	switch m.DirectAccountType {
	case "user":
		c.UserIdentifiers = &ttnpb.UserIdentifiers{UserId: m.DirectAccountFriendlyID}
	case "organization":
		if m.IndirectAccountType == "user" {
			c.UserIdentifiers = &ttnpb.UserIdentifiers{UserId: m.IndirectAccountFriendlyID}
			c.RightsOnOrganization = &indirectAccountRights
		}
		c.OrganizationIdentifiers = &ttnpb.OrganizationIdentifiers{OrganizationId: m.DirectAccountFriendlyID}
	}
	return c
}
type MembershipChain struct {
	UserIdentifiers         *ttnpb.UserIdentifiers
	RightsOnOrganization    *ttnpb.Rights
	OrganizationIdentifiers *ttnpb.OrganizationIdentifiers
	RightsOnEntity          *ttnpb.Rights
	EntityIdentifiers       *ttnpb.EntityIdentifiers
}
func (m *MembershipChain) GetRights() *ttnpb.Rights {
	if m.RightsOnOrganization == nil {
		return m.RightsOnEntity.Implied()
	}
	return m.RightsOnEntity.Implied().Intersect(m.RightsOnOrganization.Implied())
}
type MembershipChains []*MembershipChain