Skip to content

Commit

Permalink
Add full test with mocked homeserver
Browse files Browse the repository at this point in the history
  • Loading branch information
hughns committed Jan 20, 2025
1 parent cc43c8e commit 7da848d
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 18 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ require (
github.com/go-jose/go-jose/v3 v3.0.3 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/cel-go v0.21.0 // indirect
github.com/google/uuid v1.6.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ4
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
Expand Down
3 changes: 3 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ func (h *Handler) handle(w http.ResponseWriter, r *http.Request) {
return
}

// TODO: we should be sanitising the input here before using it
// e.g. only allowing `https://` URL scheme
userInfo, err := exchangeOIDCToken(r.Context(), body.OpenIDToken, h.skipVerifyTLS)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
Expand All @@ -145,6 +147,7 @@ func (h *Handler) handle(w http.ResponseWriter, r *http.Request) {

log.Printf("Got user info for %s", userInfo.Sub)

// TODO: is DeviceID required? If so then we should have validated at the start of the request processing
token, err := getJoinToken(h.key, h.secret, body.Room, userInfo.Sub+":"+body.DeviceID)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
Expand Down
151 changes: 133 additions & 18 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,16 @@
package main

import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"

"github.com/matrix-org/gomatrix"
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"testing"

"github.com/golang-jwt/jwt/v5"
"github.com/matrix-org/gomatrix"
)

func TestHealthcheck(t *testing.T) {
Expand Down Expand Up @@ -53,16 +56,96 @@ func TestHandleOptions(t *testing.T) {
if status := rr.Code; status != http.StatusOK {
t.Errorf("handler returned wrong status code for OPTIONS: got %v want %v", status, http.StatusOK)
}

if accessControlAllowOrigin := rr.Header().Get("Access-Control-Allow-Origin"); accessControlAllowOrigin != "*" {
t.Errorf("handler returned wrong Access-Control-Allow-Origin: got %v want %v", accessControlAllowOrigin, "*")
}

if accessControlAllowMethods := rr.Header().Get("Access-Control-Allow-Methods"); accessControlAllowMethods != "POST" {
t.Errorf("handler returned wrong Access-Control-Allow-Methods: got %v want %v", accessControlAllowMethods, "POST")
}
}

func TestHandlePostMissingRoom(t *testing.T) {
func TestHandlePostMissingParams(t *testing.T) {
handler := &Handler{}
body := SFURequest{
Room: "",
OpenIDToken: OpenIDTokenType{AccessToken: "token", MatrixServerName: "server"},
DeviceID: "device",

testCases := []map[string]interface{} {
{},
{
"room": "",
},
}
jsonBody, _ := json.Marshal(body)

for _, testCase := range testCases {
jsonBody, _ := json.Marshal(testCase)

req, err := http.NewRequest("POST", "/sfu/get", bytes.NewBuffer(jsonBody))
if err != nil {
t.Fatal(err)
}

rr := httptest.NewRecorder()
handler.prepareMux().ServeHTTP(rr, req)

if status := rr.Code; status != http.StatusBadRequest {
t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusBadRequest)
}

var resp gomatrix.RespError
err = json.NewDecoder(rr.Body).Decode(&resp)
if err != nil {
t.Errorf("failed to decode response body %v", err)
}

if resp.ErrCode != "M_BAD_JSON" {
t.Errorf("unexpected error code: got %v want %v", resp.ErrCode, "M_BAD_JSON")
}
}
}

func TestHandlePost(t *testing.T) {
handler := &Handler{
secret: "testSecret",
key: "testKey",
lk_url: "wss://lk.local:8080/foo",
skipVerifyTLS: true,
}

var matrixServerName = ""

testServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t.Log("Received request")
// Inspect the request
if r.URL.Path != "/_matrix/federation/v1/openid/userinfo" {
t.Errorf("unexpected request path: got %v want %v", r.URL.Path, "/_matrix/federation/v1/openid/userinfo")
}

if accessToken := r.URL.Query().Get("access_token"); accessToken != "testAccessToken" {
t.Errorf("unexpected access token: got %v want %v", accessToken, "testAccessToken")
}

// Mock response
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(fmt.Sprintf(`{"sub": "@user:%s"}`, matrixServerName)))

Check failure on line 130 in main_test.go

View workflow job for this annotation

GitHub Actions / Linting

Error return value of `w.Write` is not checked (errcheck)
}))
defer testServer.Close()

u, _ := url.Parse(testServer.URL)

matrixServerName = u.Host

testCase := map[string]interface{} {
"room": "testRoom",
"openid_token": map[string]interface{} {
"access_token": "testAccessToken",
"token_type": "testTokenType",
"matrix_server_name": u.Host,
},
"device_id": "testDevice",
}

jsonBody, _ := json.Marshal(testCase)

req, err := http.NewRequest("POST", "/sfu/get", bytes.NewBuffer(jsonBody))
if err != nil {
Expand All @@ -72,18 +155,50 @@ func TestHandlePostMissingRoom(t *testing.T) {
rr := httptest.NewRecorder()
handler.prepareMux().ServeHTTP(rr, req)

if status := rr.Code; status != http.StatusBadRequest {
t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusBadRequest)
if status := rr.Code; status != http.StatusOK {
t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK)
}

if contentType := rr.Header().Get("Content-Type"); contentType != "application/json" {
t.Errorf("handler returned wrong Content-Type: got %v want %v", contentType, "application/json")
}

var resp gomatrix.RespError
var resp SFUResponse
err = json.NewDecoder(rr.Body).Decode(&resp)
if err != nil {
t.Errorf("failed to decode response body %v", err)
}

if resp.ErrCode != "M_BAD_JSON" {
t.Errorf("unexpected error code: got %v want %v", resp.ErrCode, "M_BAD_JSON")
if resp.URL != "wss://lk.local:8080/foo" {
t.Errorf("unexpected URL: got %v want %v", resp.URL, "wss://lk.local:8080/foo")
}

if resp.JWT == "" {
t.Error("expected JWT to be non-empty")
}

// parse JWT checking the shared secret
token, err := jwt.Parse(resp.JWT, func(token *jwt.Token) (interface{}, error) {
return []byte(handler.secret), nil
})

if err != nil {
t.Fatalf("failed to parse JWT: %v", err)
}

claims, ok := token.Claims.(jwt.MapClaims)

if !ok || !token.Valid {
t.Fatalf("failed to parse claims from JWT: %v", err)
}

if claims["sub"] != "@user:"+matrixServerName+":testDevice" {
t.Errorf("unexpected sub: got %v want %v", claims["sub"], "@user:"+matrixServerName+":testDevice")
}

// should have permission for the room
if claims["video"].(map[string]interface{})["room"] != "testRoom" {
t.Errorf("unexpected room: got %v want %v", claims["room"], "testRoom")
}
}

Expand Down

0 comments on commit 7da848d

Please sign in to comment.