Skip to content
This repository has been archived by the owner on Feb 23, 2023. It is now read-only.

Commit

Permalink
Merge branch 'loginsso' into serving
Browse files Browse the repository at this point in the history
  • Loading branch information
tommie committed Oct 2, 2021
2 parents 0ae2e62 + f2bccd7 commit 4e72dd2
Show file tree
Hide file tree
Showing 40 changed files with 2,220 additions and 118 deletions.
2 changes: 1 addition & 1 deletion build/scripts/find-lint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ echo "Looking for lint..."
# Capture exit code to ensure go.{mod,sum} is restored before exiting
exit_code=0

golangci-lint run $args || exit_code=1
PATH="$PATH:${GOPATH:-~/go}/bin" golangci-lint run $args || exit_code=1

# Restore go.{mod,sum}
mv go.mod.bak go.mod && mv go.sum.bak go.sum
Expand Down
2 changes: 2 additions & 0 deletions clientapi/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ type DeviceDatabase interface {
type AccountDatabase interface {
// Look up the account matching the given localpart.
GetAccountByLocalpart(ctx context.Context, localpart string) (*api.Account, error)
GetAccountByPassword(ctx context.Context, localpart, password string) (*api.Account, error)
GetLocalpartForThreePID(ctx context.Context, address, medium string) (string, error)
}

// VerifyUserFromRequest authenticates the HTTP request,
Expand Down
2 changes: 2 additions & 0 deletions clientapi/auth/authtypes/logintypes.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ const (
LoginTypeSharedSecret = "org.matrix.login.shared_secret"
LoginTypeRecaptcha = "m.login.recaptcha"
LoginTypeApplicationService = "m.login.application_service"
LoginTypeSSO = "m.login.sso"
LoginTypeToken = "m.login.token"
)
83 changes: 83 additions & 0 deletions clientapi/auth/login.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright 2020 The Matrix.org Foundation C.I.C.
//
// 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 auth

import (
"context"
"encoding/json"
"io"
"io/ioutil"
"net/http"

"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
"github.com/matrix-org/dendrite/clientapi/jsonerror"
"github.com/matrix-org/dendrite/setup/config"
uapi "github.com/matrix-org/dendrite/userapi/api"
"github.com/matrix-org/util"
)

// LoginFromJSONReader performs authentication given a login request body reader and
// some context. It returns the basic login information and a cleanup function to be
// called after authorization has completed, with the result of the authorization.
// If the final return value is non-nil, an error occurred and the cleanup function
// is nil.
func LoginFromJSONReader(ctx context.Context, r io.Reader, accountDB AccountDatabase, userAPI UserInternalAPIForLogin, cfg *config.ClientAPI) (*Login, LoginCleanupFunc, *util.JSONResponse) {
reqBytes, err := ioutil.ReadAll(r)
if err != nil {
err := &util.JSONResponse{
Code: http.StatusBadRequest,
JSON: jsonerror.BadJSON("Reading request body failed: " + err.Error()),
}
return nil, nil, err
}

var header struct {
Type string `json:"type"`
}
if err := json.Unmarshal(reqBytes, &header); err != nil {
err := &util.JSONResponse{
Code: http.StatusBadRequest,
JSON: jsonerror.BadJSON("Reading request body failed: " + err.Error()),
}
return nil, nil, err
}

var typ Type
switch header.Type {
case authtypes.LoginTypePassword:
typ = &LoginTypePassword{
GetAccountByPassword: accountDB.GetAccountByPassword,
Config: cfg,
}
case authtypes.LoginTypeToken:
typ = &LoginTypeToken{
UserAPI: userAPI,
Config: cfg,
}
default:
err := util.JSONResponse{
Code: http.StatusBadRequest,
JSON: jsonerror.InvalidArgumentValue("unhandled login type: " + header.Type),
}
return nil, nil, &err
}

return typ.LoginFromJSON(ctx, reqBytes)
}

// UserInternalAPIForLogin contains the aspects of UserAPI required for logging in.
type UserInternalAPIForLogin interface {
uapi.LoginTokenInternalAPI
}
131 changes: 131 additions & 0 deletions clientapi/auth/login_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// Copyright 2020 The Matrix.org Foundation C.I.C.
//
// 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 auth

import (
"context"
"reflect"
"strings"
"testing"

"github.com/matrix-org/dendrite/clientapi/jsonerror"
"github.com/matrix-org/dendrite/setup/config"
uapi "github.com/matrix-org/dendrite/userapi/api"
)

func TestLoginFromJSONReader(t *testing.T) {
ctx := context.Background()

tsts := []struct {
Name string
Body string

WantErrCode string
WantUsername string
WantDeviceID string
WantDeletedTokens []string
}{
{Name: "empty", WantErrCode: "M_BAD_JSON"},
{
Name: "passwordWorks",
Body: `{
"type": "m.login.password",
"identifier": { "type": "m.id.user", "user": "alice" },
"password": "herpassword",
"device_id": "adevice"
}`,
WantUsername: "alice",
WantDeviceID: "adevice",
},
{
Name: "tokenWorks",
Body: `{
"type": "m.login.token",
"token": "atoken",
"device_id": "adevice"
}`,
WantUsername: "@auser:example.com",
WantDeviceID: "adevice",
WantDeletedTokens: []string{"atoken"},
},
}
for _, tst := range tsts {
t.Run(tst.Name, func(t *testing.T) {
var accountDB fakeAccountDB
var userAPI fakeUserInternalAPI
cfg := &config.ClientAPI{
Matrix: &config.Global{
ServerName: serverName,
},
}
login, cleanup, errRes := LoginFromJSONReader(ctx, strings.NewReader(tst.Body), &accountDB, &userAPI, cfg)
if tst.WantErrCode == "" {
if errRes != nil {
t.Fatalf("LoginFromJSONReader failed: %+v", errRes)
}
cleanup(ctx, nil)
} else {
if errRes == nil {
t.Fatalf("LoginFromJSONReader err: got %+v, want code %q", errRes, tst.WantErrCode)
} else if merr, ok := errRes.JSON.(*jsonerror.MatrixError); ok && merr.ErrCode != tst.WantErrCode {
t.Fatalf("LoginFromJSONReader err: got %+v, want code %q", errRes, tst.WantErrCode)
}
return
}

if login.Username() != tst.WantUsername {
t.Errorf("Username: got %q, want %q", login.Username(), tst.WantUsername)
}

if login.DeviceID == nil {
if tst.WantDeviceID != "" {
t.Errorf("DeviceID: got %v, want %q", login.DeviceID, tst.WantDeviceID)
}
} else {
if *login.DeviceID != tst.WantDeviceID {
t.Errorf("DeviceID: got %q, want %q", *login.DeviceID, tst.WantDeviceID)
}
}

if !reflect.DeepEqual(userAPI.DeletedTokens, tst.WantDeletedTokens) {
t.Errorf("DeletedTokens: got %+v, want %+v", userAPI.DeletedTokens, tst.WantDeletedTokens)
}
})
}
}

type fakeAccountDB struct {
AccountDatabase
}

func (*fakeAccountDB) GetAccountByPassword(ctx context.Context, localpart, password string) (*uapi.Account, error) {
return &uapi.Account{}, nil
}

type fakeUserInternalAPI struct {
UserInternalAPIForLogin

DeletedTokens []string
}

func (ua *fakeUserInternalAPI) PerformLoginTokenDeletion(ctx context.Context, req *uapi.PerformLoginTokenDeletionRequest, res *uapi.PerformLoginTokenDeletionResponse) error {
ua.DeletedTokens = append(ua.DeletedTokens, req.Token)
return nil
}

func (*fakeUserInternalAPI) QueryLoginToken(ctx context.Context, req *uapi.QueryLoginTokenRequest, res *uapi.QueryLoginTokenResponse) error {
res.Data = &uapi.LoginTokenData{UserID: "@auser:example.com"}
return nil
}
84 changes: 84 additions & 0 deletions clientapi/auth/login_token.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Copyright 2020 The Matrix.org Foundation C.I.C.
//
// 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 auth

import (
"context"
"net/http"

"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
"github.com/matrix-org/dendrite/clientapi/httputil"
"github.com/matrix-org/dendrite/clientapi/jsonerror"
"github.com/matrix-org/dendrite/setup/config"
uapi "github.com/matrix-org/dendrite/userapi/api"
"github.com/matrix-org/util"
)

// LoginTypeToken describes how to authenticate with a login token.
type LoginTypeToken struct {
UserAPI uapi.LoginTokenInternalAPI
Config *config.ClientAPI
}

// Name implements Type.
func (t *LoginTypeToken) Name() string {
return authtypes.LoginTypeToken
}

// LoginFromJSON implements Type. The cleanup function deletes the token from
// the database on success.
func (t *LoginTypeToken) LoginFromJSON(ctx context.Context, reqBytes []byte) (*Login, LoginCleanupFunc, *util.JSONResponse) {
var r loginTokenRequest
if err := httputil.UnmarshalJSON(reqBytes, &r); err != nil {
return nil, nil, err
}

return t.login(ctx, &r)
}

// loginTokenRequest struct to hold the possible parameters from an HTTP request.
type loginTokenRequest struct {
Login
Token string `json:"token"`
}

// login parses and validates the login token. It returns basic user information.
func (t *LoginTypeToken) login(ctx context.Context, r *loginTokenRequest) (*Login, LoginCleanupFunc, *util.JSONResponse) {
var res uapi.QueryLoginTokenResponse
if err := t.UserAPI.QueryLoginToken(ctx, &uapi.QueryLoginTokenRequest{Token: r.Token}, &res); err != nil {
util.GetLogger(ctx).WithError(err).Error("UserAPI.QueryLoginToken failed")
jsonErr := jsonerror.InternalServerError()
return nil, nil, &jsonErr
}
if res.Data == nil {
return nil, nil, &util.JSONResponse{
Code: http.StatusForbidden,
JSON: jsonerror.Forbidden("invalid login token"),
}
}

r.Login.Identifier.Type = "m.id.user"
r.Login.Identifier.User = res.Data.UserID

cleanup := func(ctx context.Context, authRes *util.JSONResponse) {
if authRes == nil || authRes.Code == http.StatusOK {
var res uapi.PerformLoginTokenDeletionResponse
if err := t.UserAPI.PerformLoginTokenDeletion(ctx, &uapi.PerformLoginTokenDeletionRequest{Token: r.Token}, &res); err != nil {
util.GetLogger(ctx).WithError(err).Error("UserAPI.PerformLoginTokenDeletion failed")
}
}
}
return &r.Login, cleanup, nil
}
21 changes: 16 additions & 5 deletions clientapi/auth/password.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import (
"context"
"net/http"

"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
"github.com/matrix-org/dendrite/clientapi/httputil"
"github.com/matrix-org/dendrite/clientapi/jsonerror"
"github.com/matrix-org/dendrite/clientapi/userutil"
"github.com/matrix-org/dendrite/setup/config"
Expand All @@ -39,15 +41,24 @@ type LoginTypePassword struct {
}

func (t *LoginTypePassword) Name() string {
return "m.login.password"
return authtypes.LoginTypePassword
}

func (t *LoginTypePassword) Request() interface{} {
return &PasswordRequest{}
func (t *LoginTypePassword) LoginFromJSON(ctx context.Context, reqBytes []byte) (*Login, LoginCleanupFunc, *util.JSONResponse) {
var r PasswordRequest
if err := httputil.UnmarshalJSON(reqBytes, &r); err != nil {
return nil, nil, err
}

login, err := t.Login(ctx, &r)
if err != nil {
return nil, nil, err
}

return login, func(context.Context, *util.JSONResponse) {}, nil
}

func (t *LoginTypePassword) Login(ctx context.Context, req interface{}) (*Login, *util.JSONResponse) {
r := req.(*PasswordRequest)
func (t *LoginTypePassword) Login(ctx context.Context, r *PasswordRequest) (*Login, *util.JSONResponse) {
username := r.Username()
if username == "" {
return nil, &util.JSONResponse{
Expand Down
Loading

0 comments on commit 4e72dd2

Please sign in to comment.