dendrite/clientapi/threepid/threepid.go
jjj333_p 8c6cf51b8f
Fixing Presence Conflicts (#3320)
This is meant to cache client presence for a moment so that it doesn't
oscillate.

Currently Dendrite just federates out whatever presence it gets from the
sync loop, which means if theres any clients attempting to sync without
setting the user online, and there is an online client, it will just
flip back and forth each time one of the clients polls /sync.

This pull request essentially stores in a map when the client last set
online ideally to allow the online client to sync again and set an
online presence before setting idle or offline.

I am not great at programming nor am I familiar with this codebase so if
this pr is just shitwater feel free to discard, just trying to fix an
issue that severely bothers me. If it is easier you can also steal the
code and write it in yourself. I ran the linter, not sure that it did
anything, the vscode go extension seems to format and lint anyways.

I tried to run unit tests but I have no idea any of this thing. it
errors on
`TestRequestPool_updatePresence/same_presence_is_not_published_dummy2
(10m0s)` which I think making this change broke. I am unsure how to
comply, if y'all point me in the right direction ill try to fix it. I
have tested it with all the situations I can think of on my personal
instance pain.agency, and this seems to stand up under all the
previously bugged situations.

~~My go also decided to update a bunch of the dependencies, I hate git
and github and have no idea how to fix that, it was not intentional.~~ i
just overwrote them with the ones from the main repo and committed it,
seems to have done what was needed.

### Pull Request Checklist

<!-- Please read
https://matrix-org.github.io/dendrite/development/contributing before
submitting your pull request -->

* [x] I have added Go unit tests or [Complement integration
tests](https://github.com/matrix-org/complement) for this PR _or_ I have
justified why this PR doesn't need tests
* [x] Pull request includes a [sign off below using a legally
identifiable
name](https://matrix-org.github.io/dendrite/development/contributing#sign-off)
_or_ I have already signed off privately

Signed-off-by: `Joseph Winkie <jjj333.p.1325@gmail.com>`

---------

Co-authored-by: Till Faelligen <2353100+S7evinK@users.noreply.github.com>
2024-08-03 22:03:39 +02:00

193 lines
6.2 KiB
Go

// Copyright 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 threepid
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"github.com/matrix-org/dendrite/setup/config"
"github.com/matrix-org/gomatrixserverlib/fclient"
"github.com/matrix-org/gomatrixserverlib/spec"
)
// EmailAssociationRequest represents the request defined at https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-register-email-requesttoken
type EmailAssociationRequest struct {
IDServer string `json:"id_server"`
Secret string `json:"client_secret"`
Email string `json:"email"`
SendAttempt int `json:"send_attempt"`
}
// EmailAssociationCheckRequest represents the request defined at https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-account-3pid
type EmailAssociationCheckRequest struct {
Creds Credentials `json:"three_pid_creds"`
Bind bool `json:"bind"`
}
// Credentials represents the "ThreePidCredentials" structure defined at https://matrix.org/docs/spec/client_server/r0.2.0.html#post-matrix-client-r0-account-3pid
type Credentials struct {
SID string `json:"sid"`
IDServer string `json:"id_server"`
Secret string `json:"client_secret"`
}
type SID struct {
SID string `json:"sid"`
}
// CreateSession creates a session on an identity server.
// Returns the session's ID.
// Returns an error if there was a problem sending the request or decoding the
// response, or if the identity server responded with a non-OK status.
func CreateSession(
ctx context.Context, req EmailAssociationRequest, cfg *config.ClientAPI, client *fclient.Client,
) (string, error) {
if err := isTrusted(req.IDServer, cfg); err != nil {
return "", err
}
// Create a session on the ID server
postURL := fmt.Sprintf("https://%s/_matrix/identity/api/v1/validate/email/requestToken", req.IDServer)
data := url.Values{}
data.Add("client_secret", req.Secret)
data.Add("email", req.Email)
data.Add("send_attempt", strconv.Itoa(req.SendAttempt))
request, err := http.NewRequest(http.MethodPost, postURL, strings.NewReader(data.Encode()))
if err != nil {
return "", err
}
request.Header.Add("Content-Type", "application/x-www-form-urlencoded")
resp, err := client.DoHTTPRequest(ctx, request)
if err != nil {
return "", err
}
defer resp.Body.Close() // nolint: errcheck
// Error if the status isn't OK
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("could not create a session on the server %s", req.IDServer)
}
// Extract the SID from the response and return it
var sid SID
err = json.NewDecoder(resp.Body).Decode(&sid)
return sid.SID, err
}
type GetValidatedResponse struct {
Medium string `json:"medium"`
ValidatedAt int64 `json:"validated_at"`
Address string `json:"address"`
ErrCode string `json:"errcode"`
Error string `json:"error"`
}
// CheckAssociation checks the status of an ongoing association validation on an
// identity server.
// Returns a boolean set to true if the association has been validated, false if not.
// If the association has been validated, also returns the related third-party
// identifier and its medium.
// Returns an error if there was a problem sending the request or decoding the
// response, or if the identity server responded with a non-OK status.
func CheckAssociation(
ctx context.Context, creds Credentials, cfg *config.ClientAPI,
client *fclient.Client,
) (bool, string, string, error) {
if err := isTrusted(creds.IDServer, cfg); err != nil {
return false, "", "", err
}
requestURL := fmt.Sprintf("https://%s/_matrix/identity/api/v1/3pid/getValidated3pid?sid=%s&client_secret=%s", creds.IDServer, creds.SID, creds.Secret)
req, err := http.NewRequest(http.MethodGet, requestURL, nil)
if err != nil {
return false, "", "", err
}
resp, err := client.DoHTTPRequest(ctx, req)
if err != nil {
return false, "", "", err
}
var respBody GetValidatedResponse
if err = json.NewDecoder(resp.Body).Decode(&respBody); err != nil {
return false, "", "", err
}
if respBody.ErrCode == string(spec.ErrorSessionNotValidated) {
return false, "", "", nil
} else if len(respBody.ErrCode) > 0 {
return false, "", "", errors.New(respBody.Error)
}
return true, respBody.Address, respBody.Medium, nil
}
// PublishAssociation publishes a validated association between a third-party
// identifier and a Matrix ID.
// Returns an error if there was a problem sending the request or decoding the
// response, or if the identity server responded with a non-OK status.
func PublishAssociation(ctx context.Context, creds Credentials, userID string, cfg *config.ClientAPI, client *fclient.Client) error {
if err := isTrusted(creds.IDServer, cfg); err != nil {
return err
}
postURL := fmt.Sprintf("https://%s/_matrix/identity/api/v1/3pid/bind", creds.IDServer)
data := url.Values{}
data.Add("sid", creds.SID)
data.Add("client_secret", creds.Secret)
data.Add("mxid", userID)
request, err := http.NewRequest(http.MethodPost, postURL, strings.NewReader(data.Encode()))
if err != nil {
return err
}
request.Header.Add("Content-Type", "application/x-www-form-urlencoded")
resp, err := client.DoHTTPRequest(ctx, request)
if err != nil {
return err
}
// Error if the status isn't OK
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("could not publish the association on the server %s", creds.IDServer)
}
return nil
}
// isTrusted checks if a given identity server is part of the list of trusted
// identity servers in the configuration file.
// Returns an error if the server isn't trusted.
func isTrusted(idServer string, cfg *config.ClientAPI) error {
for _, server := range cfg.Matrix.TrustedIDServers {
if idServer == server {
return nil
}
}
return ErrNotTrusted{}
}