2017-05-25 07:33:50 -05: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.
2020-02-13 11:27:33 -06:00
package sqlite3
2017-05-25 07:33:50 -05:00
import (
2017-09-18 09:51:26 -05:00
"context"
2017-05-25 07:33:50 -05:00
"database/sql"
2020-02-13 11:27:33 -06:00
"strings"
2017-05-25 07:33:50 -05:00
"time"
2020-07-22 11:04:57 -05:00
"github.com/matrix-org/dendrite/internal"
2020-06-12 08:55:57 -05:00
"github.com/matrix-org/dendrite/internal/sqlutil"
2020-06-16 08:10:55 -05:00
"github.com/matrix-org/dendrite/userapi/api"
2022-07-25 04:39:22 -05:00
"github.com/matrix-org/dendrite/userapi/storage/sqlite3/deltas"
2022-02-18 07:51:59 -06:00
"github.com/matrix-org/dendrite/userapi/storage/tables"
2020-02-13 11:27:33 -06:00
2018-05-31 09:21:13 -05:00
"github.com/matrix-org/dendrite/clientapi/userutil"
2017-05-25 07:33:50 -05:00
"github.com/matrix-org/gomatrixserverlib"
)
const devicesSchema = `
2019-08-23 11:55:40 -05:00
-- This sequence is used for automatic allocation of session_id .
2020-02-13 11:27:33 -06:00
-- CREATE SEQUENCE IF NOT EXISTS device_session_id_seq START 1 ;
2019-08-23 11:55:40 -05:00
2017-05-25 07:33:50 -05:00
-- Stores data about devices .
2022-10-18 09:59:08 -05:00
CREATE TABLE IF NOT EXISTS userapi_devices (
2020-02-13 11:27:33 -06:00
access_token TEXT PRIMARY KEY ,
session_id INTEGER ,
device_id TEXT ,
localpart TEXT ,
2022-11-11 10:41:37 -06:00
server_name TEXT NOT NULL ,
2020-02-13 11:27:33 -06:00
created_ts BIGINT ,
display_name TEXT ,
2020-10-09 03:17:23 -05:00
last_seen_ts BIGINT ,
ip TEXT ,
user_agent TEXT ,
2020-02-13 11:27:33 -06:00
2022-11-11 10:41:37 -06:00
UNIQUE ( localpart , server_name , device_id )
2017-05-25 07:33:50 -05:00
) ;
`
const insertDeviceSQL = "" +
2022-11-11 10:41:37 -06:00
"INSERT INTO userapi_devices (device_id, localpart, server_name, access_token, created_ts, display_name, session_id, last_seen_ts, ip, user_agent)" +
" VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)"
2020-02-13 11:27:33 -06:00
const selectDevicesCountSQL = "" +
2022-10-18 09:59:08 -05:00
"SELECT COUNT(access_token) FROM userapi_devices"
2017-05-25 07:33:50 -05:00
const selectDeviceByTokenSQL = "" +
2022-11-11 10:41:37 -06:00
"SELECT session_id, device_id, localpart, server_name FROM userapi_devices WHERE access_token = $1"
2017-05-25 07:33:50 -05:00
2017-10-17 13:12:54 -05:00
const selectDeviceByIDSQL = "" +
2022-11-11 10:41:37 -06:00
"SELECT display_name, last_seen_ts, ip FROM userapi_devices WHERE localpart = $1 AND server_name = $2 AND device_id = $3"
2017-10-17 13:12:54 -05:00
const selectDevicesByLocalpartSQL = "" +
2023-02-17 04:39:46 -06:00
"SELECT device_id, display_name, last_seen_ts, ip, user_agent, session_id FROM userapi_devices WHERE localpart = $1 AND server_name = $2 AND device_id != $3 ORDER BY last_seen_ts DESC"
2017-11-14 03:59:02 -06:00
const updateDeviceNameSQL = "" +
2022-11-11 10:41:37 -06:00
"UPDATE userapi_devices SET display_name = $1 WHERE localpart = $2 AND server_name = $3 AND device_id = $4"
2017-10-17 13:12:54 -05:00
2017-05-25 07:33:50 -05:00
const deleteDeviceSQL = "" +
2022-11-11 10:41:37 -06:00
"DELETE FROM userapi_devices WHERE device_id = $1 AND localpart = $2 AND server_name = $3"
2017-05-25 07:33:50 -05:00
2017-10-15 05:29:47 -05:00
const deleteDevicesByLocalpartSQL = "" +
2022-11-11 10:41:37 -06:00
"DELETE FROM userapi_devices WHERE localpart = $1 AND server_name = $2 AND device_id != $3"
2017-10-15 05:29:47 -05:00
2020-02-11 06:13:38 -06:00
const deleteDevicesSQL = "" +
2022-11-11 10:41:37 -06:00
"DELETE FROM userapi_devices WHERE localpart = $1 AND server_name = $2 AND device_id IN ($3)"
2020-02-11 06:13:38 -06:00
2020-07-22 11:04:57 -05:00
const selectDevicesByIDSQL = "" +
2023-02-17 04:39:46 -06:00
"SELECT device_id, localpart, server_name, display_name, last_seen_ts, session_id FROM userapi_devices WHERE device_id IN ($1) ORDER BY last_seen_ts DESC"
2020-07-22 11:04:57 -05:00
2020-10-09 03:17:23 -05:00
const updateDeviceLastSeen = "" +
2022-11-11 10:41:37 -06:00
"UPDATE userapi_devices SET last_seen_ts = $1, ip = $2, user_agent = $3 WHERE localpart = $4 AND server_name = $5 AND device_id = $6"
2020-10-09 03:17:23 -05:00
2017-05-25 07:33:50 -05:00
type devicesStatements struct {
2020-02-13 11:27:33 -06:00
db * sql . DB
2017-10-15 05:29:47 -05:00
insertDeviceStmt * sql . Stmt
2020-02-13 11:27:33 -06:00
selectDevicesCountStmt * sql . Stmt
2017-10-15 05:29:47 -05:00
selectDeviceByTokenStmt * sql . Stmt
2017-10-17 13:12:54 -05:00
selectDeviceByIDStmt * sql . Stmt
2020-07-22 11:04:57 -05:00
selectDevicesByIDStmt * sql . Stmt
2017-10-17 13:12:54 -05:00
selectDevicesByLocalpartStmt * sql . Stmt
2017-11-14 03:59:02 -06:00
updateDeviceNameStmt * sql . Stmt
2020-10-09 03:17:23 -05:00
updateDeviceLastSeenStmt * sql . Stmt
2017-10-15 05:29:47 -05:00
deleteDeviceStmt * sql . Stmt
deleteDevicesByLocalpartStmt * sql . Stmt
2017-10-17 13:12:54 -05:00
serverName gomatrixserverlib . ServerName
2017-05-25 07:33:50 -05:00
}
2022-02-18 07:51:59 -06:00
func NewSQLiteDevicesTable ( db * sql . DB , serverName gomatrixserverlib . ServerName ) ( tables . DevicesTable , error ) {
s := & devicesStatements {
db : db ,
serverName : serverName ,
2017-10-17 13:12:54 -05:00
}
2022-02-18 07:51:59 -06:00
_ , err := db . Exec ( devicesSchema )
if err != nil {
return nil , err
2020-10-09 03:17:23 -05:00
}
2022-07-25 04:39:22 -05:00
m := sqlutil . NewMigrator ( db )
m . AddMigrations ( sqlutil . Migration {
Version : "userapi: add last_seen_ts" ,
Up : deltas . UpLastSeenTSIP ,
} )
if err = m . Up ( context . Background ( ) ) ; err != nil {
return nil , err
}
2022-02-18 07:51:59 -06:00
return s , sqlutil . StatementList {
{ & s . insertDeviceStmt , insertDeviceSQL } ,
{ & s . selectDevicesCountStmt , selectDevicesCountSQL } ,
{ & s . selectDeviceByTokenStmt , selectDeviceByTokenSQL } ,
{ & s . selectDeviceByIDStmt , selectDeviceByIDSQL } ,
{ & s . selectDevicesByLocalpartStmt , selectDevicesByLocalpartSQL } ,
{ & s . updateDeviceNameStmt , updateDeviceNameSQL } ,
{ & s . deleteDeviceStmt , deleteDeviceSQL } ,
{ & s . deleteDevicesByLocalpartStmt , deleteDevicesByLocalpartSQL } ,
{ & s . selectDevicesByIDStmt , selectDevicesByIDSQL } ,
{ & s . updateDeviceLastSeenStmt , updateDeviceLastSeen } ,
} . Prepare ( db )
2017-05-25 07:33:50 -05:00
}
// insertDevice creates a new device. Returns an error if any device with the same access token already exists.
// Returns an error if the user already has a device with the given device ID.
// Returns the device on success.
2022-02-18 07:51:59 -06:00
func ( s * devicesStatements ) InsertDevice (
2022-11-11 10:41:37 -06:00
ctx context . Context , txn * sql . Tx , id string ,
localpart string , serverName gomatrixserverlib . ServerName ,
accessToken string , displayName * string , ipAddr , userAgent string ,
2020-06-16 08:10:55 -05:00
) ( * api . Device , error ) {
2017-05-25 07:33:50 -05:00
createdTimeMS := time . Now ( ) . UnixNano ( ) / 1000000
2019-08-23 11:55:40 -05:00
var sessionID int64
2020-08-21 04:42:08 -05:00
countStmt := sqlutil . TxStmt ( txn , s . selectDevicesCountStmt )
insertStmt := sqlutil . TxStmt ( txn , s . insertDeviceStmt )
if err := countStmt . QueryRowContext ( ctx ) . Scan ( & sessionID ) ; err != nil {
return nil , err
}
sessionID ++
2022-11-11 10:41:37 -06:00
if _ , err := insertStmt . ExecContext ( ctx , id , localpart , serverName , accessToken , createdTimeMS , displayName , sessionID , createdTimeMS , ipAddr , userAgent ) ; err != nil {
2017-09-18 09:51:26 -05:00
return nil , err
2017-05-25 07:33:50 -05:00
}
2020-06-16 08:10:55 -05:00
return & api . Device {
2017-09-18 09:51:26 -05:00
ID : id ,
2022-11-11 10:41:37 -06:00
UserID : userutil . MakeUserID ( localpart , serverName ) ,
2017-09-18 09:51:26 -05:00
AccessToken : accessToken ,
2019-08-23 11:55:40 -05:00
SessionID : sessionID ,
2020-10-09 03:17:23 -05:00
LastSeenTS : createdTimeMS ,
LastSeenIP : ipAddr ,
UserAgent : userAgent ,
2017-09-18 09:51:26 -05:00
} , nil
2017-05-25 07:33:50 -05:00
}
2023-02-17 04:39:46 -06:00
func ( s * devicesStatements ) InsertDeviceWithSessionID ( ctx context . Context , txn * sql . Tx , id ,
localpart string , serverName gomatrixserverlib . ServerName ,
accessToken string , displayName * string , ipAddr , userAgent string ,
sessionID int64 ,
) ( * api . Device , error ) {
createdTimeMS := time . Now ( ) . UnixNano ( ) / 1000000
insertStmt := sqlutil . TxStmt ( txn , s . insertDeviceStmt )
if _ , err := insertStmt . ExecContext ( ctx , id , localpart , serverName , accessToken , createdTimeMS , displayName , sessionID , createdTimeMS , ipAddr , userAgent ) ; err != nil {
return nil , err
}
return & api . Device {
ID : id ,
UserID : userutil . MakeUserID ( localpart , serverName ) ,
AccessToken : accessToken ,
SessionID : sessionID ,
LastSeenTS : createdTimeMS ,
LastSeenIP : ipAddr ,
UserAgent : userAgent ,
} , nil
}
2022-02-18 07:51:59 -06:00
func ( s * devicesStatements ) DeleteDevice (
2022-11-11 10:41:37 -06:00
ctx context . Context , txn * sql . Tx , id string ,
localpart string , serverName gomatrixserverlib . ServerName ,
2017-09-18 09:51:26 -05:00
) error {
2020-08-21 04:42:08 -05:00
stmt := sqlutil . TxStmt ( txn , s . deleteDeviceStmt )
2022-11-11 10:41:37 -06:00
_ , err := stmt . ExecContext ( ctx , id , localpart , serverName )
2020-08-21 04:42:08 -05:00
return err
2017-05-25 07:33:50 -05:00
}
2022-02-18 07:51:59 -06:00
func ( s * devicesStatements ) DeleteDevices (
2022-11-11 10:41:37 -06:00
ctx context . Context , txn * sql . Tx ,
localpart string , serverName gomatrixserverlib . ServerName ,
devices [ ] string ,
2020-02-11 06:13:38 -06:00
) error {
2022-11-11 10:41:37 -06:00
orig := strings . Replace ( deleteDevicesSQL , "($3)" , sqlutil . QueryVariadicOffset ( len ( devices ) , 2 ) , 1 )
2020-02-13 11:27:33 -06:00
prep , err := s . db . Prepare ( orig )
if err != nil {
return err
}
2020-08-21 04:42:08 -05:00
stmt := sqlutil . TxStmt ( txn , prep )
2022-11-11 10:41:37 -06:00
params := make ( [ ] interface { } , len ( devices ) + 2 )
2020-08-21 04:42:08 -05:00
params [ 0 ] = localpart
2022-11-11 10:41:37 -06:00
params [ 1 ] = serverName
2020-08-21 04:42:08 -05:00
for i , v := range devices {
2022-11-11 10:41:37 -06:00
params [ i + 2 ] = v
2020-08-21 04:42:08 -05:00
}
_ , err = stmt . ExecContext ( ctx , params ... )
return err
2020-02-11 06:13:38 -06:00
}
2022-02-18 07:51:59 -06:00
func ( s * devicesStatements ) DeleteDevicesByLocalpart (
2022-11-11 10:41:37 -06:00
ctx context . Context , txn * sql . Tx ,
localpart string , serverName gomatrixserverlib . ServerName ,
exceptDeviceID string ,
2017-10-15 05:29:47 -05:00
) error {
2020-08-21 04:42:08 -05:00
stmt := sqlutil . TxStmt ( txn , s . deleteDevicesByLocalpartStmt )
2022-11-11 10:41:37 -06:00
_ , err := stmt . ExecContext ( ctx , localpart , serverName , exceptDeviceID )
2020-08-21 04:42:08 -05:00
return err
2017-10-15 05:29:47 -05:00
}
2022-02-18 07:51:59 -06:00
func ( s * devicesStatements ) UpdateDeviceName (
2022-11-11 10:41:37 -06:00
ctx context . Context , txn * sql . Tx ,
localpart string , serverName gomatrixserverlib . ServerName ,
deviceID string , displayName * string ,
2017-11-14 03:59:02 -06:00
) error {
2020-08-21 04:42:08 -05:00
stmt := sqlutil . TxStmt ( txn , s . updateDeviceNameStmt )
2022-11-11 10:41:37 -06:00
_ , err := stmt . ExecContext ( ctx , displayName , localpart , serverName , deviceID )
2020-08-21 04:42:08 -05:00
return err
2017-11-14 03:59:02 -06:00
}
2022-02-18 07:51:59 -06:00
func ( s * devicesStatements ) SelectDeviceByToken (
2017-09-18 09:51:26 -05:00
ctx context . Context , accessToken string ,
2020-06-16 08:10:55 -05:00
) ( * api . Device , error ) {
var dev api . Device
2017-05-25 07:33:50 -05:00
var localpart string
2022-11-11 10:41:37 -06:00
var serverName gomatrixserverlib . ServerName
2017-09-18 09:51:26 -05:00
stmt := s . selectDeviceByTokenStmt
2022-11-11 10:41:37 -06:00
err := stmt . QueryRowContext ( ctx , accessToken ) . Scan ( & dev . SessionID , & dev . ID , & localpart , & serverName )
2017-05-25 07:33:50 -05:00
if err == nil {
2022-11-11 10:41:37 -06:00
dev . UserID = userutil . MakeUserID ( localpart , serverName )
2017-05-25 07:33:50 -05:00
dev . AccessToken = accessToken
}
return & dev , err
}
2019-07-22 09:05:38 -05:00
// selectDeviceByID retrieves a device from the database with the given user
// localpart and deviceID
2022-02-18 07:51:59 -06:00
func ( s * devicesStatements ) SelectDeviceByID (
2022-11-11 10:41:37 -06:00
ctx context . Context ,
localpart string , serverName gomatrixserverlib . ServerName ,
deviceID string ,
2020-06-16 08:10:55 -05:00
) ( * api . Device , error ) {
var dev api . Device
2022-04-28 08:06:34 -05:00
var displayName , ip sql . NullString
2017-10-17 13:12:54 -05:00
stmt := s . selectDeviceByIDStmt
2022-04-28 08:06:34 -05:00
var lastseenTS sql . NullInt64
2022-11-11 10:41:37 -06:00
err := stmt . QueryRowContext ( ctx , localpart , serverName , deviceID ) . Scan ( & displayName , & lastseenTS , & ip )
2017-10-17 13:12:54 -05:00
if err == nil {
dev . ID = deviceID
2022-11-11 10:41:37 -06:00
dev . UserID = userutil . MakeUserID ( localpart , serverName )
2020-07-22 11:04:57 -05:00
if displayName . Valid {
dev . DisplayName = displayName . String
}
2022-04-28 08:06:34 -05:00
if lastseenTS . Valid {
dev . LastSeenTS = lastseenTS . Int64
}
if ip . Valid {
dev . LastSeenIP = ip . String
}
2017-10-17 13:12:54 -05:00
}
return & dev , err
}
2022-02-18 07:51:59 -06:00
func ( s * devicesStatements ) SelectDevicesByLocalpart (
2022-11-11 10:41:37 -06:00
ctx context . Context , txn * sql . Tx ,
localpart string , serverName gomatrixserverlib . ServerName ,
exceptDeviceID string ,
2020-06-16 08:10:55 -05:00
) ( [ ] api . Device , error ) {
devices := [ ] api . Device { }
2022-11-11 10:41:37 -06:00
rows , err := sqlutil . TxStmt ( txn , s . selectDevicesByLocalpartStmt ) . QueryContext ( ctx , localpart , serverName , exceptDeviceID )
2017-10-17 13:12:54 -05:00
if err != nil {
return devices , err
}
2022-04-27 08:05:49 -05:00
var dev api . Device
var lastseents sql . NullInt64
var id , displayname , ip , useragent sql . NullString
2017-10-17 13:12:54 -05:00
for rows . Next ( ) {
2023-02-17 04:39:46 -06:00
err = rows . Scan ( & id , & displayname , & lastseents , & ip , & useragent , & dev . SessionID )
2017-10-17 13:12:54 -05:00
if err != nil {
return devices , err
}
2020-05-26 08:41:16 -05:00
if id . Valid {
dev . ID = id . String
}
if displayname . Valid {
dev . DisplayName = displayname . String
}
2020-11-17 04:07:03 -06:00
if lastseents . Valid {
dev . LastSeenTS = lastseents . Int64
}
if ip . Valid {
dev . LastSeenIP = ip . String
}
if useragent . Valid {
dev . UserAgent = useragent . String
}
2022-11-11 10:41:37 -06:00
dev . UserID = userutil . MakeUserID ( localpart , serverName )
2017-10-17 13:12:54 -05:00
devices = append ( devices , dev )
}
2020-02-13 11:27:33 -06:00
return devices , nil
2017-10-17 13:12:54 -05:00
}
2020-07-22 11:04:57 -05:00
2022-02-18 07:51:59 -06:00
func ( s * devicesStatements ) SelectDevicesByID ( ctx context . Context , deviceIDs [ ] string ) ( [ ] api . Device , error ) {
2020-07-22 11:04:57 -05:00
sqlQuery := strings . Replace ( selectDevicesByIDSQL , "($1)" , sqlutil . QueryVariadic ( len ( deviceIDs ) ) , 1 )
iDeviceIDs := make ( [ ] interface { } , len ( deviceIDs ) )
for i := range deviceIDs {
iDeviceIDs [ i ] = deviceIDs [ i ]
}
rows , err := s . db . QueryContext ( ctx , sqlQuery , iDeviceIDs ... )
if err != nil {
return nil , err
}
defer internal . CloseAndLogIfError ( ctx , rows , "selectDevicesByID: rows.close() failed" )
var devices [ ] api . Device
2022-04-27 08:05:49 -05:00
var dev api . Device
var localpart string
2022-11-11 10:41:37 -06:00
var serverName gomatrixserverlib . ServerName
2022-04-27 08:05:49 -05:00
var displayName sql . NullString
var lastseents sql . NullInt64
2020-07-22 11:04:57 -05:00
for rows . Next ( ) {
2023-02-17 04:39:46 -06:00
if err := rows . Scan ( & dev . ID , & localpart , & serverName , & displayName , & lastseents , & dev . SessionID ) ; err != nil {
2020-07-22 11:04:57 -05:00
return nil , err
}
if displayName . Valid {
dev . DisplayName = displayName . String
}
2022-04-27 08:05:49 -05:00
if lastseents . Valid {
dev . LastSeenTS = lastseents . Int64
}
2022-11-11 10:41:37 -06:00
dev . UserID = userutil . MakeUserID ( localpart , serverName )
2020-07-22 11:04:57 -05:00
devices = append ( devices , dev )
}
return devices , rows . Err ( )
}
2020-10-09 03:17:23 -05:00
2022-11-11 10:41:37 -06:00
func ( s * devicesStatements ) UpdateDeviceLastSeen ( ctx context . Context , txn * sql . Tx , localpart string , serverName gomatrixserverlib . ServerName , deviceID , ipAddr , userAgent string ) error {
2020-10-09 03:17:23 -05:00
lastSeenTs := time . Now ( ) . UnixNano ( ) / 1000000
stmt := sqlutil . TxStmt ( txn , s . updateDeviceLastSeenStmt )
2022-11-11 10:41:37 -06:00
_ , err := stmt . ExecContext ( ctx , lastSeenTs , ipAddr , userAgent , localpart , serverName , deviceID )
2020-10-09 03:17:23 -05:00
return err
}