From 6d15aec8d31d8dc565fb8fc2137bd20edc8fab3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20T=C3=B6tterman?= Date: Tue, 17 Oct 2017 21:12:54 +0300 Subject: [PATCH] Add /devices/ and /device/{deviceID} (#313) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Paul Tötterman --- .../auth/storage/devices/devices_table.go | 55 ++++++++++- .../clientapi/auth/storage/devices/storage.go | 15 +++ .../dendrite/clientapi/routing/device.go | 97 +++++++++++++++++++ .../dendrite/clientapi/routing/routing.go | 13 +++ 4 files changed, 178 insertions(+), 2 deletions(-) create mode 100644 src/github.com/matrix-org/dendrite/clientapi/routing/device.go diff --git a/src/github.com/matrix-org/dendrite/clientapi/auth/storage/devices/devices_table.go b/src/github.com/matrix-org/dendrite/clientapi/auth/storage/devices/devices_table.go index 62932b65..a614eb54 100644 --- a/src/github.com/matrix-org/dendrite/clientapi/auth/storage/devices/devices_table.go +++ b/src/github.com/matrix-org/dendrite/clientapi/auth/storage/devices/devices_table.go @@ -54,6 +54,12 @@ const insertDeviceSQL = "" + const selectDeviceByTokenSQL = "" + "SELECT device_id, localpart FROM device_devices WHERE access_token = $1" +const selectDeviceByIDSQL = "" + + "SELECT created_ts FROM device_devices WHERE localpart = $1 and device_id = $2" + +const selectDevicesByLocalpartSQL = "" + + "SELECT device_id FROM device_devices WHERE localpart = $1" + const deleteDeviceSQL = "" + "DELETE FROM device_devices WHERE device_id = $1 AND localpart = $2" @@ -65,10 +71,11 @@ const deleteDevicesByLocalpartSQL = "" + type devicesStatements struct { insertDeviceStmt *sql.Stmt selectDeviceByTokenStmt *sql.Stmt + selectDeviceByIDStmt *sql.Stmt + selectDevicesByLocalpartStmt *sql.Stmt deleteDeviceStmt *sql.Stmt deleteDevicesByLocalpartStmt *sql.Stmt - - serverName gomatrixserverlib.ServerName + serverName gomatrixserverlib.ServerName } func (s *devicesStatements) prepare(db *sql.DB, server gomatrixserverlib.ServerName) (err error) { @@ -82,6 +89,12 @@ func (s *devicesStatements) prepare(db *sql.DB, server gomatrixserverlib.ServerN if s.selectDeviceByTokenStmt, err = db.Prepare(selectDeviceByTokenSQL); err != nil { return } + if s.selectDeviceByIDStmt, err = db.Prepare(selectDeviceByIDSQL); err != nil { + return + } + if s.selectDevicesByLocalpartStmt, err = db.Prepare(selectDevicesByLocalpartSQL); err != nil { + return + } if s.deleteDeviceStmt, err = db.Prepare(deleteDeviceSQL); err != nil { return } @@ -140,6 +153,44 @@ func (s *devicesStatements) selectDeviceByToken( return &dev, err } +func (s *devicesStatements) selectDeviceByID( + ctx context.Context, localpart, deviceID string, +) (*authtypes.Device, error) { + var dev authtypes.Device + var created int64 + stmt := s.selectDeviceByIDStmt + err := stmt.QueryRowContext(ctx, localpart, deviceID).Scan(&created) + if err == nil { + dev.ID = deviceID + dev.UserID = makeUserID(localpart, s.serverName) + } + return &dev, err +} + +func (s *devicesStatements) selectDevicesByLocalpart( + ctx context.Context, localpart string, +) ([]authtypes.Device, error) { + devices := []authtypes.Device{} + + rows, err := s.selectDevicesByLocalpartStmt.QueryContext(ctx, localpart) + + if err != nil { + return devices, err + } + + for rows.Next() { + var dev authtypes.Device + err = rows.Scan(&dev.ID) + if err != nil { + return devices, err + } + dev.UserID = makeUserID(localpart, s.serverName) + devices = append(devices, dev) + } + + return devices, nil +} + func makeUserID(localpart string, server gomatrixserverlib.ServerName) string { return fmt.Sprintf("@%s:%s", localpart, string(server)) } diff --git a/src/github.com/matrix-org/dendrite/clientapi/auth/storage/devices/storage.go b/src/github.com/matrix-org/dendrite/clientapi/auth/storage/devices/storage.go index c100e8f5..dd98bb60 100644 --- a/src/github.com/matrix-org/dendrite/clientapi/auth/storage/devices/storage.go +++ b/src/github.com/matrix-org/dendrite/clientapi/auth/storage/devices/storage.go @@ -52,6 +52,21 @@ func (d *Database) GetDeviceByAccessToken( return d.devices.selectDeviceByToken(ctx, token) } +// GetDeviceByID returns the device matching the given ID. +// Returns sql.ErrNoRows if no matching device was found. +func (d *Database) GetDeviceByID( + ctx context.Context, localpart, deviceID string, +) (*authtypes.Device, error) { + return d.devices.selectDeviceByID(ctx, localpart, deviceID) +} + +// GetDevicesByLocalpart returns the devices matching the given localpart. +func (d *Database) GetDevicesByLocalpart( + ctx context.Context, localpart string, +) ([]authtypes.Device, error) { + return d.devices.selectDevicesByLocalpart(ctx, localpart) +} + // CreateDevice makes a new device associated with the given user ID localpart. // If there is already a device with the same device ID for this user, that access token will be revoked // and replaced with the given accessToken. If the given accessToken is already in use for another device, diff --git a/src/github.com/matrix-org/dendrite/clientapi/routing/device.go b/src/github.com/matrix-org/dendrite/clientapi/routing/device.go new file mode 100644 index 00000000..9cb63bac --- /dev/null +++ b/src/github.com/matrix-org/dendrite/clientapi/routing/device.go @@ -0,0 +1,97 @@ +// Copyright 2017 Paul Tötterman +// +// 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 routing + +import ( + "database/sql" + "net/http" + + "github.com/matrix-org/dendrite/clientapi/auth/authtypes" + "github.com/matrix-org/dendrite/clientapi/auth/storage/devices" + "github.com/matrix-org/dendrite/clientapi/httputil" + "github.com/matrix-org/dendrite/clientapi/jsonerror" + "github.com/matrix-org/gomatrixserverlib" + "github.com/matrix-org/util" +) + +type deviceJSON struct { + DeviceID string `json:"device_id"` + UserID string `json:"user_id"` +} + +type devicesJSON struct { + Devices []deviceJSON `json:"devices"` +} + +// GetDeviceByID handles /device/{deviceID} +func GetDeviceByID( + req *http.Request, deviceDB *devices.Database, device *authtypes.Device, + deviceID string, +) util.JSONResponse { + localpart, _, err := gomatrixserverlib.SplitID('@', device.UserID) + if err != nil { + return httputil.LogThenError(req, err) + } + + ctx := req.Context() + dev, err := deviceDB.GetDeviceByID(ctx, localpart, deviceID) + if err == sql.ErrNoRows { + return util.JSONResponse{ + Code: 404, + JSON: jsonerror.NotFound("Unknown device"), + } + } else if err != nil { + return httputil.LogThenError(req, err) + } + + return util.JSONResponse{ + Code: 200, + JSON: deviceJSON{ + DeviceID: dev.ID, + UserID: dev.UserID, + }, + } +} + +// GetDevicesByLocalpart handles /devices +func GetDevicesByLocalpart( + req *http.Request, deviceDB *devices.Database, device *authtypes.Device, +) util.JSONResponse { + localpart, _, err := gomatrixserverlib.SplitID('@', device.UserID) + if err != nil { + return httputil.LogThenError(req, err) + } + + ctx := req.Context() + devices, err := deviceDB.GetDevicesByLocalpart(ctx, localpart) + + if err != nil { + return httputil.LogThenError(req, err) + } + + res := devicesJSON{} + + for _, dev := range devices { + res.Devices = append(res.Devices, deviceJSON{ + DeviceID: dev.ID, + UserID: dev.UserID, + }) + } + + return util.JSONResponse{ + Code: 200, + JSON: res, + } +} diff --git a/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go b/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go index 04c183f3..ebf48ad6 100644 --- a/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go +++ b/src/github.com/matrix-org/dendrite/clientapi/routing/routing.go @@ -355,6 +355,19 @@ func Setup( }), ).Methods("PUT", "OPTIONS") + r0mux.Handle("/devices", + common.MakeAuthAPI("get_devices", deviceDB, func(req *http.Request, device *authtypes.Device) util.JSONResponse { + return GetDevicesByLocalpart(req, deviceDB, device) + }), + ).Methods("GET") + + r0mux.Handle("/device/{deviceID}", + common.MakeAuthAPI("get_device", deviceDB, func(req *http.Request, device *authtypes.Device) util.JSONResponse { + vars := mux.Vars(req) + return GetDeviceByID(req, deviceDB, device, vars["deviceID"]) + }), + ).Methods("GET") + // Stub implementations for sytest r0mux.Handle("/events", common.MakeExternalAPI("events", func(req *http.Request) util.JSONResponse {