From b86b55360a6b46b5edc0f6d049fd997f46a9b074 Mon Sep 17 00:00:00 2001 From: Anant Prakash Date: Fri, 1 Jun 2018 16:46:19 +0530 Subject: [PATCH] Add app service authentication functions (#433) * Add support for AS ?user= parameter in auth Signed-off-by: Anant Prakash * Fix typo --- .../dendrite/clientapi/auth/auth.go | 88 +++++++++++++++---- .../auth/storage/accounts/storage.go | 8 ++ .../clientapi/auth/storage/devices/storage.go | 20 ++++- 3 files changed, 99 insertions(+), 17 deletions(-) diff --git a/src/github.com/matrix-org/dendrite/clientapi/auth/auth.go b/src/github.com/matrix-org/dendrite/clientapi/auth/auth.go index f44b6eea..5a74f3f4 100644 --- a/src/github.com/matrix-org/dendrite/clientapi/auth/auth.go +++ b/src/github.com/matrix-org/dendrite/clientapi/auth/auth.go @@ -27,6 +27,8 @@ import ( "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/common/config" "github.com/matrix-org/util" ) @@ -34,15 +36,83 @@ import ( // 32 bytes => 256 bits var tokenByteLength = 32 -// The length of generated device IDs -var deviceIDByteLength = 6 - // DeviceDatabase represents a device database. type DeviceDatabase interface { // Look up the device matching the given access token. GetDeviceByAccessToken(ctx context.Context, token string) (*authtypes.Device, error) } +// AccountDatabase represents an account database. +type AccountDatabase interface { + // Look up the account matching the given localpart. + GetAccountByLocalpart(ctx context.Context, localpart string) (*authtypes.Account, error) +} + +// VerifyUserFromRequest authenticates the HTTP request, +// on success returns UserID of the requester. +// Finds local user or an application service user. +// On failure returns an JSON error response which can be sent to the client. +func VerifyUserFromRequest( + req *http.Request, accountDB AccountDatabase, deviceDB DeviceDatabase, + applicationServices []config.ApplicationService, +) (string, *util.JSONResponse) { + // Try to find local user from device database + dev, devErr := VerifyAccessToken(req, deviceDB) + + if devErr == nil { + return dev.UserID, nil + } + + // Try to find the Application Service user + token, err := extractAccessToken(req) + + if err != nil { + return "", &util.JSONResponse{ + Code: http.StatusUnauthorized, + JSON: jsonerror.MissingToken(err.Error()), + } + } + + // Search for app service with given access_token + var appService *config.ApplicationService + for _, as := range applicationServices { + if as.ASToken == token { + appService = &as + break + } + } + + if appService != nil { + userID := req.URL.Query().Get("user_id") + localpart, err := userutil.ParseUsernameParam(userID, nil) + + if err != nil { + return "", &util.JSONResponse{ + Code: http.StatusBadRequest, + JSON: jsonerror.InvalidUsername(err.Error()), + } + } + + // Verify that the user is registered + account, accountErr := accountDB.GetAccountByLocalpart(req.Context(), localpart) + + // Verify that account exists & appServiceID matches + if accountErr == nil && account.AppServiceID == appService.ID { + return userID, nil + } + + return "", &util.JSONResponse{ + Code: http.StatusForbidden, + JSON: jsonerror.Forbidden("Application service has not registered this user"), + } + } + + return "", &util.JSONResponse{ + Code: http.StatusUnauthorized, + JSON: jsonerror.UnknownToken("Unrecognized access token"), + } +} + // VerifyAccessToken verifies that an access token was supplied in the given HTTP request // and returns the device it corresponds to. Returns resErr (an error response which can be // sent to the client) if the token is invalid or there was a problem querying the database. @@ -81,18 +151,6 @@ func GenerateAccessToken() (string, error) { return base64.RawURLEncoding.EncodeToString(b), nil } -// GenerateDeviceID creates a new device id. Returns an error if failed to generate -// random bytes. -func GenerateDeviceID() (string, error) { - b := make([]byte, deviceIDByteLength) - _, err := rand.Read(b) - if err != nil { - return "", err - } - // url-safe no padding - return base64.RawURLEncoding.EncodeToString(b), nil -} - // extractAccessToken from a request, or return an error detailing what went wrong. The // error message MUST be human-readable and comprehensible to the client. func extractAccessToken(req *http.Request) (string, error) { diff --git a/src/github.com/matrix-org/dendrite/clientapi/auth/storage/accounts/storage.go b/src/github.com/matrix-org/dendrite/clientapi/auth/storage/accounts/storage.go index fc82ec75..d696eb65 100644 --- a/src/github.com/matrix-org/dendrite/clientapi/auth/storage/accounts/storage.go +++ b/src/github.com/matrix-org/dendrite/clientapi/auth/storage/accounts/storage.go @@ -365,3 +365,11 @@ func (d *Database) CheckAccountAvailability(ctx context.Context, localpart strin } return false, err } + +// GetAccountByLocalpart returns the account associated with the given localpart. +// This function assumes the request is authenticated or the account data is used only internally. +// Returns sql.ErrNoRows if no account exists which matches the given localpart. +func (d *Database) GetAccountByLocalpart(ctx context.Context, localpart string, +) (*authtypes.Account, error) { + return d.accounts.selectAccountByLocalpart(ctx, localpart) +} 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 6ac475a6..7683a427 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 @@ -16,14 +16,18 @@ package devices import ( "context" + "crypto/rand" "database/sql" + "encoding/base64" - "github.com/matrix-org/dendrite/clientapi/auth" "github.com/matrix-org/dendrite/clientapi/auth/authtypes" "github.com/matrix-org/dendrite/common" "github.com/matrix-org/gomatrixserverlib" ) +// The length of generated device IDs +var deviceIDByteLength = 6 + // Database represents a device database. type Database struct { db *sql.DB @@ -93,7 +97,7 @@ func (d *Database) CreateDevice( // We cap this at going round 5 times to ensure we don't spin forever var newDeviceID string for i := 1; i <= 5; i++ { - newDeviceID, returnErr = auth.GenerateDeviceID() + newDeviceID, returnErr = generateDeviceID() if returnErr != nil { return } @@ -111,6 +115,18 @@ func (d *Database) CreateDevice( return } +// generateDeviceID creates a new device id. Returns an error if failed to generate +// random bytes. +func generateDeviceID() (string, error) { + b := make([]byte, deviceIDByteLength) + _, err := rand.Read(b) + if err != nil { + return "", err + } + // url-safe no padding + return base64.RawURLEncoding.EncodeToString(b), nil +} + // UpdateDevice updates the given device with the display name. // Returns SQL error if there are problems and nil on success. func (d *Database) UpdateDevice(