mirror of
https://github.com/matrix-org/dendrite.git
synced 2026-01-01 03:03:10 -06:00
Only allow device deletion if the session matches
This commit is contained in:
parent
cf27e26712
commit
5510ccaa78
|
|
@ -25,10 +25,12 @@ import (
|
||||||
"github.com/matrix-org/dendrite/userapi/api"
|
"github.com/matrix-org/dendrite/userapi/api"
|
||||||
"github.com/matrix-org/gomatrixserverlib"
|
"github.com/matrix-org/gomatrixserverlib"
|
||||||
"github.com/matrix-org/util"
|
"github.com/matrix-org/util"
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
)
|
)
|
||||||
|
|
||||||
// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-devices
|
// https://matrix.org/docs/spec/client_server/r0.6.1#get-matrix-client-r0-devices
|
||||||
type deviceJSON struct {
|
type deviceJSON struct {
|
||||||
|
UserID string `json:"user_id"`
|
||||||
DeviceID string `json:"device_id"`
|
DeviceID string `json:"device_id"`
|
||||||
DisplayName string `json:"display_name"`
|
DisplayName string `json:"display_name"`
|
||||||
LastSeenIP string `json:"last_seen_ip"`
|
LastSeenIP string `json:"last_seen_ip"`
|
||||||
|
|
@ -77,6 +79,7 @@ func GetDeviceByID(
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusOK,
|
Code: http.StatusOK,
|
||||||
JSON: deviceJSON{
|
JSON: deviceJSON{
|
||||||
|
UserID: targetDevice.UserID,
|
||||||
DeviceID: targetDevice.ID,
|
DeviceID: targetDevice.ID,
|
||||||
DisplayName: targetDevice.DisplayName,
|
DisplayName: targetDevice.DisplayName,
|
||||||
LastSeenIP: stripIPPort(targetDevice.LastSeenIP),
|
LastSeenIP: stripIPPort(targetDevice.LastSeenIP),
|
||||||
|
|
@ -102,6 +105,7 @@ func GetDevicesByLocalpart(
|
||||||
|
|
||||||
for _, dev := range queryRes.Devices {
|
for _, dev := range queryRes.Devices {
|
||||||
res.Devices = append(res.Devices, deviceJSON{
|
res.Devices = append(res.Devices, deviceJSON{
|
||||||
|
UserID: dev.UserID,
|
||||||
DeviceID: dev.ID,
|
DeviceID: dev.ID,
|
||||||
DisplayName: dev.DisplayName,
|
DisplayName: dev.DisplayName,
|
||||||
LastSeenIP: stripIPPort(dev.LastSeenIP),
|
LastSeenIP: stripIPPort(dev.LastSeenIP),
|
||||||
|
|
@ -163,6 +167,15 @@ func DeleteDeviceById(
|
||||||
req *http.Request, userInteractiveAuth *auth.UserInteractive, userAPI api.UserInternalAPI, device *api.Device,
|
req *http.Request, userInteractiveAuth *auth.UserInteractive, userAPI api.UserInternalAPI, device *api.Device,
|
||||||
deviceID string,
|
deviceID string,
|
||||||
) util.JSONResponse {
|
) util.JSONResponse {
|
||||||
|
var (
|
||||||
|
deleteOK bool
|
||||||
|
sessionID string
|
||||||
|
)
|
||||||
|
defer func() {
|
||||||
|
if deleteOK {
|
||||||
|
sessions.deleteSession(sessionID)
|
||||||
|
}
|
||||||
|
}()
|
||||||
ctx := req.Context()
|
ctx := req.Context()
|
||||||
defer req.Body.Close() // nolint:errcheck
|
defer req.Body.Close() // nolint:errcheck
|
||||||
bodyBytes, err := ioutil.ReadAll(req.Body)
|
bodyBytes, err := ioutil.ReadAll(req.Body)
|
||||||
|
|
@ -172,8 +185,29 @@ func DeleteDeviceById(
|
||||||
JSON: jsonerror.BadJSON("The request body could not be read: " + err.Error()),
|
JSON: jsonerror.BadJSON("The request body could not be read: " + err.Error()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check that we know this session, and it matches with the device to delete
|
||||||
|
s := gjson.GetBytes(bodyBytes, "auth.session").Str
|
||||||
|
if dev, ok := sessions.getDeviceToDelete(s); ok {
|
||||||
|
if dev != deviceID {
|
||||||
|
return util.JSONResponse{
|
||||||
|
Code: http.StatusForbidden,
|
||||||
|
JSON: jsonerror.Forbidden("session & device mismatch"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if s != "" {
|
||||||
|
sessionID = s
|
||||||
|
}
|
||||||
|
|
||||||
login, errRes := userInteractiveAuth.Verify(ctx, bodyBytes, device)
|
login, errRes := userInteractiveAuth.Verify(ctx, bodyBytes, device)
|
||||||
if errRes != nil {
|
if errRes != nil {
|
||||||
|
switch data := errRes.JSON.(type) {
|
||||||
|
case auth.Challenge:
|
||||||
|
sessions.addDeviceToDelete(data.Session, deviceID)
|
||||||
|
default:
|
||||||
|
}
|
||||||
return *errRes
|
return *errRes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -201,6 +235,8 @@ func DeleteDeviceById(
|
||||||
return jsonerror.InternalServerError()
|
return jsonerror.InternalServerError()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deleteOK = true
|
||||||
|
|
||||||
return util.JSONResponse{
|
return util.JSONResponse{
|
||||||
Code: http.StatusOK,
|
Code: http.StatusOK,
|
||||||
JSON: struct{}{},
|
JSON: struct{}{},
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,7 @@ type sessionsDict struct {
|
||||||
sessions map[string][]authtypes.LoginType
|
sessions map[string][]authtypes.LoginType
|
||||||
params map[string]registerRequest
|
params map[string]registerRequest
|
||||||
timer map[string]*time.Timer
|
timer map[string]*time.Timer
|
||||||
|
sessionToDevice map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
// defaultTimeout is the timeout used to clean up sessions
|
// defaultTimeout is the timeout used to clean up sessions
|
||||||
|
|
@ -115,6 +116,7 @@ func (d *sessionsDict) deleteSession(sessionID string) {
|
||||||
defer d.Unlock()
|
defer d.Unlock()
|
||||||
delete(d.params, sessionID)
|
delete(d.params, sessionID)
|
||||||
delete(d.sessions, sessionID)
|
delete(d.sessions, sessionID)
|
||||||
|
delete(d.sessionToDevice, sessionID)
|
||||||
// stop the timer, e.g. because the registration was completed
|
// stop the timer, e.g. because the registration was completed
|
||||||
if t, ok := d.timer[sessionID]; ok {
|
if t, ok := d.timer[sessionID]; ok {
|
||||||
if !t.Stop() {
|
if !t.Stop() {
|
||||||
|
|
@ -132,6 +134,7 @@ func newSessionsDict() *sessionsDict {
|
||||||
sessions: make(map[string][]authtypes.LoginType),
|
sessions: make(map[string][]authtypes.LoginType),
|
||||||
params: make(map[string]registerRequest),
|
params: make(map[string]registerRequest),
|
||||||
timer: make(map[string]*time.Timer),
|
timer: make(map[string]*time.Timer),
|
||||||
|
sessionToDevice: make(map[string]string),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -165,6 +168,20 @@ func (d *sessionsDict) addCompletedSessionStage(sessionID string, stage authtype
|
||||||
d.sessions[sessionID] = append(sessions.sessions[sessionID], stage)
|
d.sessions[sessionID] = append(sessions.sessions[sessionID], stage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *sessionsDict) addDeviceToDelete(sessionID, deviceID string) {
|
||||||
|
d.startTimer(defaultTimeOut, sessionID)
|
||||||
|
d.Lock()
|
||||||
|
defer d.Unlock()
|
||||||
|
d.sessionToDevice[sessionID] = deviceID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *sessionsDict) getDeviceToDelete(sessionID string) (string, bool) {
|
||||||
|
d.RLock()
|
||||||
|
defer d.RUnlock()
|
||||||
|
deviceID, ok := d.sessionToDevice[sessionID]
|
||||||
|
return deviceID, ok
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
sessions = newSessionsDict()
|
sessions = newSessionsDict()
|
||||||
validUsernameRegex = regexp.MustCompile(`^[0-9a-z_\-=./]+$`)
|
validUsernameRegex = regexp.MustCompile(`^[0-9a-z_\-=./]+$`)
|
||||||
|
|
|
||||||
|
|
@ -242,6 +242,7 @@ func TestSessionCleanUp(t *testing.T) {
|
||||||
s.addParams(dummySession, registerRequest{Username: "Testing"})
|
s.addParams(dummySession, registerRequest{Username: "Testing"})
|
||||||
s.addCompletedSessionStage(dummySession, authtypes.LoginTypeRecaptcha)
|
s.addCompletedSessionStage(dummySession, authtypes.LoginTypeRecaptcha)
|
||||||
s.addCompletedSessionStage(dummySession, authtypes.LoginTypeDummy)
|
s.addCompletedSessionStage(dummySession, authtypes.LoginTypeDummy)
|
||||||
|
s.addDeviceToDelete(dummySession, "dummyDevice")
|
||||||
s.getCompletedStages(dummySession)
|
s.getCompletedStages(dummySession)
|
||||||
// reset the timer with a lower timeout
|
// reset the timer with a lower timeout
|
||||||
s.startTimer(time.Millisecond, dummySession)
|
s.startTimer(time.Millisecond, dummySession)
|
||||||
|
|
@ -249,5 +250,14 @@ func TestSessionCleanUp(t *testing.T) {
|
||||||
if data, ok := s.getParams(dummySession); ok {
|
if data, ok := s.getParams(dummySession); ok {
|
||||||
t.Errorf("expected session to be deleted: %+v", data)
|
t.Errorf("expected session to be deleted: %+v", data)
|
||||||
}
|
}
|
||||||
|
if _, ok := s.timer[dummySession]; ok {
|
||||||
|
t.Error("expected timer to be delete")
|
||||||
|
}
|
||||||
|
if _, ok := s.sessions[dummySession]; ok {
|
||||||
|
t.Error("expected session to be delete")
|
||||||
|
}
|
||||||
|
if _, ok := s.getDeviceToDelete(dummySession); ok {
|
||||||
|
t.Error("expected session to device to be delete")
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -604,4 +604,4 @@ Remote banned user is kicked and may not rejoin until unbanned
|
||||||
registration remembers parameters
|
registration remembers parameters
|
||||||
registration accepts non-ascii passwords
|
registration accepts non-ascii passwords
|
||||||
registration with inhibit_login inhibits login
|
registration with inhibit_login inhibits login
|
||||||
|
The operation must be consistent through an interactive authentication session
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue