2017-09-01 11:13:10 +02:00
// 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 (
2017-09-18 15:15:27 +02:00
"context"
2017-09-01 11:13:10 +02:00
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
2017-09-11 20:18:19 +02:00
2020-12-02 18:41:00 +01:00
"github.com/matrix-org/dendrite/setup/config"
2017-09-01 11:13:10 +02:00
)
// 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:"threePidCreds" `
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" `
}
// 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.
2017-09-18 15:15:27 +02:00
func CreateSession (
2020-08-10 15:18:04 +02:00
ctx context . Context , req EmailAssociationRequest , cfg * config . ClientAPI ,
2017-09-18 15:15:27 +02:00
) ( string , error ) {
2017-09-11 20:18:19 +02:00
if err := isTrusted ( req . IDServer , cfg ) ; err != nil {
return "" , err
}
2017-09-01 11:13:10 +02:00
// 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 ) )
2018-03-13 16:55:45 +01:00
request , err := http . NewRequest ( http . MethodPost , postURL , strings . NewReader ( data . Encode ( ) ) )
2017-09-01 11:13:10 +02:00
if err != nil {
return "" , err
}
request . Header . Add ( "Content-Type" , "application/x-www-form-urlencoded" )
client := http . Client { }
2017-09-18 15:15:27 +02:00
resp , err := client . Do ( request . WithContext ( ctx ) )
2017-09-01 11:13:10 +02:00
if err != nil {
return "" , err
}
// Error if the status isn't OK
if resp . StatusCode != http . StatusOK {
2021-09-08 18:31:03 +02:00
return "" , fmt . Errorf ( "could not create a session on the server %s" , req . IDServer )
2017-09-01 11:13:10 +02:00
}
// Extract the SID from the response and return it
var sid struct {
SID string ` json:"sid" `
}
err = json . NewDecoder ( resp . Body ) . Decode ( & sid )
return sid . SID , err
}
// 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.
2017-09-18 15:15:27 +02:00
func CheckAssociation (
2020-08-10 15:18:04 +02:00
ctx context . Context , creds Credentials , cfg * config . ClientAPI ,
2017-09-18 15:15:27 +02:00
) ( bool , string , string , error ) {
2017-09-11 20:18:19 +02:00
if err := isTrusted ( creds . IDServer , cfg ) ; err != nil {
return false , "" , "" , err
}
2018-04-20 16:50:44 +02:00
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 )
2017-09-18 15:15:27 +02:00
if err != nil {
return false , "" , "" , err
}
resp , err := http . DefaultClient . Do ( req . WithContext ( ctx ) )
2017-09-01 11:13:10 +02:00
if err != nil {
return false , "" , "" , err
}
var respBody struct {
Medium string ` json:"medium" `
ValidatedAt int64 ` json:"validated_at" `
Address string ` json:"address" `
ErrCode string ` json:"errcode" `
Error string ` json:"error" `
}
if err = json . NewDecoder ( resp . Body ) . Decode ( & respBody ) ; err != nil {
return false , "" , "" , err
}
if respBody . ErrCode == "M_SESSION_NOT_VALIDATED" {
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.
2020-08-10 15:18:04 +02:00
func PublishAssociation ( creds Credentials , userID string , cfg * config . ClientAPI ) error {
2017-09-11 20:18:19 +02:00
if err := isTrusted ( creds . IDServer , cfg ) ; err != nil {
return err
}
2017-09-01 11:13:10 +02:00
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 )
2018-03-13 16:55:45 +01:00
request , err := http . NewRequest ( http . MethodPost , postURL , strings . NewReader ( data . Encode ( ) ) )
2017-09-01 11:13:10 +02:00
if err != nil {
return err
}
request . Header . Add ( "Content-Type" , "application/x-www-form-urlencoded" )
client := http . Client { }
resp , err := client . Do ( request )
if err != nil {
return err
}
// Error if the status isn't OK
if resp . StatusCode != http . StatusOK {
2021-09-08 18:31:03 +02:00
return fmt . Errorf ( "could not publish the association on the server %s" , creds . IDServer )
2017-09-01 11:13:10 +02:00
}
return nil
}
2017-09-11 20:18:19 +02:00
// 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.
2020-08-10 15:18:04 +02:00
func isTrusted ( idServer string , cfg * config . ClientAPI ) error {
2017-09-11 20:18:19 +02:00
for _ , server := range cfg . Matrix . TrustedIDServers {
if idServer == server {
return nil
}
}
return ErrNotTrusted
}