mirror of
https://github.com/1f349/dendrite.git
synced 2024-11-25 21:21:35 +00:00
Update gomatrixserverlib and use AuthEventProvider (#35)
This commit is contained in:
parent
8ccff1e40f
commit
414ea314a6
@ -151,7 +151,7 @@ func createRoom(req *http.Request, cfg config.ClientAPI, roomID string, producer
|
|||||||
// TODO m.room.aliases
|
// TODO m.room.aliases
|
||||||
}
|
}
|
||||||
|
|
||||||
authEvents := authEventProvider{builtEventMap}
|
authEvents := gomatrixserverlib.NewAuthEvents(nil)
|
||||||
for i, e := range eventsToMake {
|
for i, e := range eventsToMake {
|
||||||
depth := i + 1 // depth starts at 1
|
depth := i + 1 // depth starts at 1
|
||||||
|
|
||||||
@ -178,6 +178,7 @@ func createRoom(req *http.Request, cfg config.ClientAPI, roomID string, producer
|
|||||||
// Add the event to the list of auth events
|
// Add the event to the list of auth events
|
||||||
builtEventMap[common.StateKeyTuple{e.Type, e.StateKey}] = ev
|
builtEventMap[common.StateKeyTuple{e.Type, e.StateKey}] = ev
|
||||||
builtEvents = append(builtEvents, ev)
|
builtEvents = append(builtEvents, ev)
|
||||||
|
authEvents.AddEvent(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
// send events to the room server
|
// send events to the room server
|
||||||
@ -280,27 +281,3 @@ func eventsToMessages(events []*gomatrixserverlib.Event, topic string) ([]*saram
|
|||||||
}
|
}
|
||||||
return msgs, nil
|
return msgs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type authEventProvider struct {
|
|
||||||
events map[common.StateKeyTuple]*gomatrixserverlib.Event
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *authEventProvider) Create() (ev *gomatrixserverlib.Event, err error) {
|
|
||||||
return a.events[common.StateKeyTuple{"m.room.create", ""}], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *authEventProvider) JoinRules() (ev *gomatrixserverlib.Event, err error) {
|
|
||||||
return a.events[common.StateKeyTuple{"m.room.join_rules", ""}], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *authEventProvider) PowerLevels() (ev *gomatrixserverlib.Event, err error) {
|
|
||||||
return a.events[common.StateKeyTuple{"m.room.power_levels", ""}], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *authEventProvider) Member(stateKey string) (ev *gomatrixserverlib.Event, err error) {
|
|
||||||
return a.events[common.StateKeyTuple{"m.room.member", stateKey}], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *authEventProvider) ThirdPartyInvite(stateKey string) (ev *gomatrixserverlib.Event, err error) {
|
|
||||||
return a.events[common.StateKeyTuple{"m.room.third_party_invite", stateKey}], nil
|
|
||||||
}
|
|
||||||
|
@ -44,27 +44,27 @@ type authEvents struct {
|
|||||||
events eventMap
|
events eventMap
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create implements gomatrixserverlib.AuthEvents
|
// Create implements gomatrixserverlib.AuthEventProvider
|
||||||
func (ae *authEvents) Create() (*gomatrixserverlib.Event, error) {
|
func (ae *authEvents) Create() (*gomatrixserverlib.Event, error) {
|
||||||
return ae.lookupEventWithEmptyStateKey(types.MRoomCreateNID), nil
|
return ae.lookupEventWithEmptyStateKey(types.MRoomCreateNID), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PowerLevels implements gomatrixserverlib.AuthEvents
|
// PowerLevels implements gomatrixserverlib.AuthEventProvider
|
||||||
func (ae *authEvents) PowerLevels() (*gomatrixserverlib.Event, error) {
|
func (ae *authEvents) PowerLevels() (*gomatrixserverlib.Event, error) {
|
||||||
return ae.lookupEventWithEmptyStateKey(types.MRoomPowerLevelsNID), nil
|
return ae.lookupEventWithEmptyStateKey(types.MRoomPowerLevelsNID), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// JoinRules implements gomatrixserverlib.AuthEvents
|
// JoinRules implements gomatrixserverlib.AuthEventProvider
|
||||||
func (ae *authEvents) JoinRules() (*gomatrixserverlib.Event, error) {
|
func (ae *authEvents) JoinRules() (*gomatrixserverlib.Event, error) {
|
||||||
return ae.lookupEventWithEmptyStateKey(types.MRoomJoinRulesNID), nil
|
return ae.lookupEventWithEmptyStateKey(types.MRoomJoinRulesNID), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Memmber implements gomatrixserverlib.AuthEvents
|
// Memmber implements gomatrixserverlib.AuthEventProvider
|
||||||
func (ae *authEvents) Member(stateKey string) (*gomatrixserverlib.Event, error) {
|
func (ae *authEvents) Member(stateKey string) (*gomatrixserverlib.Event, error) {
|
||||||
return ae.lookupEvent(types.MRoomMemberNID, stateKey), nil
|
return ae.lookupEvent(types.MRoomMemberNID, stateKey), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ThirdPartyInvite implements gomatrixserverlib.AuthEvents
|
// ThirdPartyInvite implements gomatrixserverlib.AuthEventProvider
|
||||||
func (ae *authEvents) ThirdPartyInvite(stateKey string) (*gomatrixserverlib.Event, error) {
|
func (ae *authEvents) ThirdPartyInvite(stateKey string) (*gomatrixserverlib.Event, error) {
|
||||||
return ae.lookupEvent(types.MRoomThirdPartyInviteNID, stateKey), nil
|
return ae.lookupEvent(types.MRoomThirdPartyInviteNID, stateKey), nil
|
||||||
}
|
}
|
||||||
|
2
vendor/manifest
vendored
2
vendor/manifest
vendored
@ -92,7 +92,7 @@
|
|||||||
{
|
{
|
||||||
"importpath": "github.com/matrix-org/gomatrixserverlib",
|
"importpath": "github.com/matrix-org/gomatrixserverlib",
|
||||||
"repository": "https://github.com/matrix-org/gomatrixserverlib",
|
"repository": "https://github.com/matrix-org/gomatrixserverlib",
|
||||||
"revision": "4218890fdd60e73cc5539ec40b86fd51568f4a19",
|
"revision": "131b3e83fe053bc40f6909226b8c3c1d186799c1",
|
||||||
"branch": "master"
|
"branch": "master"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -160,21 +160,76 @@ func thirdPartyInviteToken(thirdPartyInviteData rawJSON) (string, error) {
|
|||||||
return thirdPartyInvite.Signed.Token, nil
|
return thirdPartyInvite.Signed.Token, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthEvents are the state events needed to authenticate an event.
|
// AuthEventProvider provides auth_events for the authentication checks.
|
||||||
type AuthEvents interface {
|
type AuthEventProvider interface {
|
||||||
// Create returns the m.room.create event for the room.
|
// Create returns the m.room.create event for the room or nil if there isn't a m.room.create event.
|
||||||
Create() (*Event, error)
|
Create() (*Event, error)
|
||||||
// JoinRules returns the m.room.join_rules event for the room.
|
// 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)
|
JoinRules() (*Event, error)
|
||||||
// PowerLevels returns the m.room.power_levels event for the room.
|
// 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)
|
PowerLevels() (*Event, error)
|
||||||
// Member returns the m.room.member event for the given user_id state_key.
|
// 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)
|
Member(stateKey string) (*Event, error)
|
||||||
// ThirdPartyInvite returns the m.room.third_party_invite event for the
|
// ThirdPartyInvite returns the m.room.third_party_invite event for the
|
||||||
// given state_key
|
// given state_key or nil if there isn't a m.room.third_party_invite event
|
||||||
ThirdPartyInvite(stateKey string) (*Event, error)
|
ThirdPartyInvite(stateKey string) (*Event, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type stateKeyTuple struct {
|
||||||
|
Type string
|
||||||
|
StateKey string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 %s 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{"m.room.create", ""}], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// JoinRules implements AuthEventProvider
|
||||||
|
func (a *AuthEvents) JoinRules() (*Event, error) {
|
||||||
|
return a.events[stateKeyTuple{"m.room.join_rules", ""}], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PowerLevels implements AuthEventProvider
|
||||||
|
func (a *AuthEvents) PowerLevels() (*Event, error) {
|
||||||
|
return a.events[stateKeyTuple{"m.room.power_levels", ""}], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Member implements AuthEventProvider
|
||||||
|
func (a *AuthEvents) Member(stateKey string) (*Event, error) {
|
||||||
|
return a.events[stateKeyTuple{"m.room.member", stateKey}], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ThirdPartyInvite implements AuthEventProvider
|
||||||
|
func (a *AuthEvents) ThirdPartyInvite(stateKey string) (*Event, error) {
|
||||||
|
return a.events[stateKeyTuple{"m.room.third_party_invite", 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)
|
||||||
|
}
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
// A NotAllowed error is returned if an event does not pass the auth checks.
|
// A NotAllowed error is returned if an event does not pass the auth checks.
|
||||||
type NotAllowed struct {
|
type NotAllowed struct {
|
||||||
Message string
|
Message string
|
||||||
@ -191,7 +246,7 @@ func errorf(message string, args ...interface{}) error {
|
|||||||
// Allowed checks whether an event is allowed by the auth events.
|
// Allowed checks whether an event is allowed by the auth events.
|
||||||
// It returns a NotAllowed error if the event is not allowed.
|
// 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.
|
// If there was an error loading the auth events then it returns that error.
|
||||||
func Allowed(event Event, authEvents AuthEvents) error {
|
func Allowed(event Event, authEvents AuthEventProvider) error {
|
||||||
switch event.Type() {
|
switch event.Type() {
|
||||||
case "m.room.create":
|
case "m.room.create":
|
||||||
return createEventAllowed(event)
|
return createEventAllowed(event)
|
||||||
@ -233,7 +288,7 @@ func createEventAllowed(event Event) error {
|
|||||||
|
|
||||||
// memberEventAllowed checks whether the m.room.member event is allowed.
|
// memberEventAllowed checks whether the m.room.member event is allowed.
|
||||||
// Membership events have different authentication rules to ordinary events.
|
// Membership events have different authentication rules to ordinary events.
|
||||||
func memberEventAllowed(event Event, authEvents AuthEvents) error {
|
func memberEventAllowed(event Event, authEvents AuthEventProvider) error {
|
||||||
allower, err := newMembershipAllower(authEvents, event)
|
allower, err := newMembershipAllower(authEvents, event)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -243,7 +298,7 @@ func memberEventAllowed(event Event, authEvents AuthEvents) error {
|
|||||||
|
|
||||||
// aliasEventAllowed checks whether the m.room.aliases event is allowed.
|
// aliasEventAllowed checks whether the m.room.aliases event is allowed.
|
||||||
// Alias events have different authentication rules to ordinary events.
|
// Alias events have different authentication rules to ordinary events.
|
||||||
func aliasEventAllowed(event Event, authEvents AuthEvents) error {
|
func aliasEventAllowed(event Event, authEvents AuthEventProvider) error {
|
||||||
// The alias events have different auth rules to ordinary events.
|
// 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.
|
// 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.
|
// This allows server admins to update the m.room.aliases event for their server when they change the aliases on their server.
|
||||||
@ -278,7 +333,7 @@ func aliasEventAllowed(event Event, authEvents AuthEvents) error {
|
|||||||
// powerLevelsEventAllowed checks whether the m.room.power_levels event is allowed.
|
// 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
|
// It returns an error if the event is not allowed or if there was a problem
|
||||||
// loading the auth events needed.
|
// loading the auth events needed.
|
||||||
func powerLevelsEventAllowed(event Event, authEvents AuthEvents) error {
|
func powerLevelsEventAllowed(event Event, authEvents AuthEventProvider) error {
|
||||||
allower, err := newEventAllower(authEvents, event.Sender())
|
allower, err := newEventAllower(authEvents, event.Sender())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -492,7 +547,7 @@ func checkUserLevels(senderLevel int64, senderID string, oldPowerLevels, newPowe
|
|||||||
// redactEventAllowed checks whether the m.room.redaction event is allowed.
|
// 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
|
// It returns an error if the event is not allowed or if there was a problem
|
||||||
// loading the auth events needed.
|
// loading the auth events needed.
|
||||||
func redactEventAllowed(event Event, authEvents AuthEvents) error {
|
func redactEventAllowed(event Event, authEvents AuthEventProvider) error {
|
||||||
allower, err := newEventAllower(authEvents, event.Sender())
|
allower, err := newEventAllower(authEvents, event.Sender())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -542,7 +597,7 @@ func redactEventAllowed(event Event, authEvents AuthEvents) error {
|
|||||||
// checks for events.
|
// checks for events.
|
||||||
// It returns an error if the event is not allowed or if there was a
|
// It returns an error if the event is not allowed or if there was a
|
||||||
// problem loading the auth events needed.
|
// problem loading the auth events needed.
|
||||||
func defaultEventAllowed(event Event, authEvents AuthEvents) error {
|
func defaultEventAllowed(event Event, authEvents AuthEventProvider) error {
|
||||||
allower, err := newEventAllower(authEvents, event.Sender())
|
allower, err := newEventAllower(authEvents, event.Sender())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -564,7 +619,7 @@ type eventAllower struct {
|
|||||||
|
|
||||||
// newEventAllower loads the information needed to authorise an event sent
|
// newEventAllower loads the information needed to authorise an event sent
|
||||||
// by a given user ID from the auth events.
|
// by a given user ID from the auth events.
|
||||||
func newEventAllower(authEvents AuthEvents, senderID string) (e eventAllower, err error) {
|
func newEventAllower(authEvents AuthEventProvider, senderID string) (e eventAllower, err error) {
|
||||||
if e.create, err = newCreateContentFromAuthEvents(authEvents); err != nil {
|
if e.create, err = newCreateContentFromAuthEvents(authEvents); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -646,7 +701,7 @@ type membershipAllower struct {
|
|||||||
|
|
||||||
// newMembershipAllower loads the information needed to authenticate the m.room.member event
|
// newMembershipAllower loads the information needed to authenticate the m.room.member event
|
||||||
// from the auth events.
|
// from the auth events.
|
||||||
func newMembershipAllower(authEvents AuthEvents, event Event) (m membershipAllower, err error) {
|
func newMembershipAllower(authEvents AuthEventProvider, event Event) (m membershipAllower, err error) {
|
||||||
stateKey := event.StateKey()
|
stateKey := event.StateKey()
|
||||||
if stateKey == nil {
|
if stateKey == nil {
|
||||||
err = errorf("m.room.member must be a state event")
|
err = errorf("m.room.member must be a state event")
|
||||||
|
@ -844,3 +844,46 @@ func TestRedactAllowed(t *testing.T) {
|
|||||||
}]
|
}]
|
||||||
}`)
|
}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAuthEvents(t *testing.T) {
|
||||||
|
power, err := NewEventFromTrustedJSON(rawJSON(`{
|
||||||
|
"type": "m.room.power_levels",
|
||||||
|
"state_key": "",
|
||||||
|
"sender": "@u1:a",
|
||||||
|
"room_id": "!r1:a",
|
||||||
|
"event_id": "$e5:a",
|
||||||
|
"content": {
|
||||||
|
"users": {
|
||||||
|
"@u1:a": 100
|
||||||
|
},
|
||||||
|
"redact": 100
|
||||||
|
}
|
||||||
|
}`), false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("TestAuthEvents: failed to create power_levels event: %s", err)
|
||||||
|
}
|
||||||
|
a := NewAuthEvents([]*Event{&power})
|
||||||
|
var e *Event
|
||||||
|
if e, err = a.PowerLevels(); err != nil || e != &power {
|
||||||
|
t.Errorf("TestAuthEvents: failed to get same power_levels event")
|
||||||
|
}
|
||||||
|
create, err := NewEventFromTrustedJSON(rawJSON(`{
|
||||||
|
"type": "m.room.create",
|
||||||
|
"state_key": "",
|
||||||
|
"sender": "@u1:a",
|
||||||
|
"room_id": "!r1:a",
|
||||||
|
"event_id": "$e1:a",
|
||||||
|
"content": {
|
||||||
|
"creator": "@u1:a"
|
||||||
|
}
|
||||||
|
}`), false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("TestAuthEvents: failed to create create event: %s", err)
|
||||||
|
}
|
||||||
|
if err = a.AddEvent(&create); err != nil {
|
||||||
|
t.Errorf("TestAuthEvents: Failed to AddEvent: %s", err)
|
||||||
|
}
|
||||||
|
if e, err = a.Create(); err != nil || e != &create {
|
||||||
|
t.Errorf("TestAuthEvents: failed to get same create event")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -39,7 +39,7 @@ type createContent struct {
|
|||||||
|
|
||||||
// newCreateContentFromAuthEvents loads the create event content from the create event in the
|
// newCreateContentFromAuthEvents loads the create event content from the create event in the
|
||||||
// auth events.
|
// auth events.
|
||||||
func newCreateContentFromAuthEvents(authEvents AuthEvents) (c createContent, err error) {
|
func newCreateContentFromAuthEvents(authEvents AuthEventProvider) (c createContent, err error) {
|
||||||
var createEvent *Event
|
var createEvent *Event
|
||||||
if createEvent, err = authEvents.Create(); err != nil {
|
if createEvent, err = authEvents.Create(); err != nil {
|
||||||
return
|
return
|
||||||
@ -113,7 +113,7 @@ type memberContent struct {
|
|||||||
|
|
||||||
// newMemberContentFromAuthEvents loads the member content from the member event for the user ID in the auth events.
|
// newMemberContentFromAuthEvents loads the member content from the member event for the user ID in the auth events.
|
||||||
// Returns an error if there was an error loading the member event or parsing the event content.
|
// Returns an error if there was an error loading the member event or parsing the event content.
|
||||||
func newMemberContentFromAuthEvents(authEvents AuthEvents, userID string) (c memberContent, err error) {
|
func newMemberContentFromAuthEvents(authEvents AuthEventProvider, userID string) (c memberContent, err error) {
|
||||||
var memberEvent *Event
|
var memberEvent *Event
|
||||||
if memberEvent, err = authEvents.Member(userID); err != nil {
|
if memberEvent, err = authEvents.Member(userID); err != nil {
|
||||||
return
|
return
|
||||||
@ -146,7 +146,7 @@ type joinRuleContent struct {
|
|||||||
|
|
||||||
// newJoinRuleContentFromAuthEvents loads the join rule content from the join rules event in the auth event.
|
// newJoinRuleContentFromAuthEvents loads the join rule content from the join rules event in the auth event.
|
||||||
// Returns an error if there was an error loading the join rule event or parsing the content.
|
// Returns an error if there was an error loading the join rule event or parsing the content.
|
||||||
func newJoinRuleContentFromAuthEvents(authEvents AuthEvents) (c joinRuleContent, err error) {
|
func newJoinRuleContentFromAuthEvents(authEvents AuthEventProvider) (c joinRuleContent, err error) {
|
||||||
var joinRulesEvent *Event
|
var joinRulesEvent *Event
|
||||||
if joinRulesEvent, err = authEvents.JoinRules(); err != nil {
|
if joinRulesEvent, err = authEvents.JoinRules(); err != nil {
|
||||||
return
|
return
|
||||||
@ -210,7 +210,7 @@ func (c *powerLevelContent) eventLevel(eventType string, isState bool) int64 {
|
|||||||
// newPowerLevelContentFromAuthEvents loads the power level content from the
|
// newPowerLevelContentFromAuthEvents loads the power level content from the
|
||||||
// power level event in the auth events or returns the default values if there
|
// power level event in the auth events or returns the default values if there
|
||||||
// is no power level event.
|
// is no power level event.
|
||||||
func newPowerLevelContentFromAuthEvents(authEvents AuthEvents, creatorUserID string) (c powerLevelContent, err error) {
|
func newPowerLevelContentFromAuthEvents(authEvents AuthEventProvider, creatorUserID string) (c powerLevelContent, err error) {
|
||||||
powerLevelsEvent, err := authEvents.PowerLevels()
|
powerLevelsEvent, err := authEvents.PowerLevels()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
Loading…
Reference in New Issue
Block a user