Guest user registration

Signed-off-by: Thibaut CHARLES cromfr@gmail.com
This commit is contained in:
Crom (Thibaut CHARLES) 2019-10-11 20:43:16 +02:00
parent 145921f207
commit 2f2c18ae84
No known key found for this signature in database
GPG key ID: 45A3D5F880B9E6D0
8 changed files with 102 additions and 23 deletions

View file

@ -118,7 +118,7 @@ func generateAppServiceAccount(
ctx := context.Background()
// Create an account for the application service
acc, err := accountsDB.CreateAccount(ctx, as.SenderLocalpart, "", as.ID)
acc, err := accountsDB.CreateAccount(ctx, false, as.SenderLocalpart, "", as.ID)
if err != nil {
return err
} else if acc == nil {

View file

@ -36,16 +36,18 @@ CREATE TABLE IF NOT EXISTS account_accounts (
-- The password hash for this account. Can be NULL if this is a passwordless account.
password_hash TEXT,
-- Identifies which application service this account belongs to, if any.
appservice_id TEXT
appservice_id TEXT,
-- Whether it is a guest account with limited access or not
is_guest BOOL
-- TODO:
-- is_guest, is_admin, upgraded_ts, devices, any email reset stuff?
-- is_admin, upgraded_ts, devices, any email reset stuff?
);
-- Create sequence for autogenerated numeric usernames
CREATE SEQUENCE IF NOT EXISTS numeric_username_seq START 1;
`
const insertAccountSQL = "" +
"INSERT INTO account_accounts(localpart, created_ts, password_hash, appservice_id) VALUES ($1, $2, $3, $4)"
"INSERT INTO account_accounts(localpart, created_ts, password_hash, appservice_id, is_guest) VALUES ($1, $2, $3, $4, $5)"
const selectAccountByLocalpartSQL = "" +
"SELECT localpart, appservice_id FROM account_accounts WHERE localpart = $1"
@ -91,16 +93,16 @@ func (s *accountsStatements) prepare(db *sql.DB, server gomatrixserverlib.Server
// this account will be passwordless. Returns an error if this account already exists. Returns the account
// on success.
func (s *accountsStatements) insertAccount(
ctx context.Context, localpart, hash, appserviceID string,
ctx context.Context, isGuest bool, localpart, hash, appserviceID string,
) (*authtypes.Account, error) {
createdTimeMS := time.Now().UnixNano() / 1000000
stmt := s.insertAccountStmt
var err error
if appserviceID == "" {
_, err = stmt.ExecContext(ctx, localpart, createdTimeMS, hash, nil)
_, err = stmt.ExecContext(ctx, localpart, createdTimeMS, hash, nil, isGuest)
} else {
_, err = stmt.ExecContext(ctx, localpart, createdTimeMS, hash, appserviceID)
_, err = stmt.ExecContext(ctx, localpart, createdTimeMS, hash, appserviceID, isGuest)
}
if err != nil {
return nil, err

View file

@ -122,7 +122,7 @@ func (d *Database) SetDisplayName(
// for this account. If no password is supplied, the account will be a passwordless account. If the
// account already exists, it will return nil, nil.
func (d *Database) CreateAccount(
ctx context.Context, localpart, plaintextPassword, appserviceID string,
ctx context.Context, isGuest bool, localpart, plaintextPassword, appserviceID string,
) (*authtypes.Account, error) {
var err error
@ -140,7 +140,7 @@ func (d *Database) CreateAccount(
}
return nil, err
}
return d.accounts.insertAccount(ctx, localpart, hash, appserviceID)
return d.accounts.insertAccount(ctx, isGuest, localpart, hash, appserviceID)
}
// SaveMembership saves the user matching a given localpart as a member of a given

View file

@ -56,6 +56,12 @@ var (
Help: "Total number of registered users",
},
)
amtGuestUsers = prometheus.NewCounter(
prometheus.CounterOpts{
Name: "dendrite_clientapi_guest_users_total",
Help: "Total number of guest users",
},
)
)
const (
@ -68,6 +74,7 @@ const (
func init() {
// Register prometheus metrics. They must be registered to be exposed.
prometheus.MustRegister(amtRegUsers)
prometheus.MustRegister(amtGuestUsers)
}
// sessionsDict keeps track of completed auth stages for each session.
@ -443,18 +450,24 @@ func Register(
deviceDB *devices.Database,
cfg *config.Dendrite,
) util.JSONResponse {
var r registerRequest
resErr := httputil.UnmarshalJSONRequest(req, &r)
if resErr != nil {
return *resErr
}
// Retrieve or generate the sessionID
sessionID := r.Auth.Session
if sessionID == "" {
// Generate a new, random session ID
sessionID = util.RandomString(sessionIDLength)
var isGuest bool
switch req.URL.Query().Get("kind") {
case "user", "":
isGuest = false
case "guest":
isGuest = true
// Guest registration ignores all JSON body except initial_device_display_name
r = registerRequest{
InitialDisplayName: r.InitialDisplayName,
}
default:
return util.MessageResponse(http.StatusBadRequest, "kind must be either 'user' or 'guest'")
}
// Don't allow numeric usernames less than MAX_INT64.
@ -497,13 +510,28 @@ func Register(
logger := util.GetLogger(req.Context())
logger.WithFields(log.Fields{
"guest": isGuest,
"username": r.Username,
"auth.type": r.Auth.Type,
"session_id": r.Auth.Session,
}).Info("Processing registration request")
if isGuest {
// Immediately register a guest account
return handleGuestRegistration(req, r, cfg, accountDB, deviceDB)
} else {
// Start registration flow
// Retrieve or generate the sessionID
sessionID := r.Auth.Session
if sessionID == "" {
// Generate a new, random session ID
sessionID = util.RandomString(sessionIDLength)
}
return handleRegistrationFlow(req, r, sessionID, cfg, accountDB, deviceDB)
}
}
// handleRegistrationFlow will direct and complete registration flow stages
// that the client has requested.
@ -597,6 +625,27 @@ func handleRegistrationFlow(
req, r, sessionID, cfg, accountDB, deviceDB)
}
// Registers immediately a guest account, if guest registration is enabled on the server
func handleGuestRegistration(
req *http.Request,
r registerRequest,
cfg *config.Dendrite,
accountDB *accounts.Database,
deviceDB *devices.Database,
) util.JSONResponse {
// Exit if guest registration is forbidden
if cfg.Matrix.GuestAccessDisabled {
return util.MessageResponse(http.StatusForbidden, "Guest access has been disabled")
}
// Create guest user
return completeRegistration(
req.Context(), accountDB, deviceDB, true, r.Username, r.Password, "",
r.InhibitLogin, r.InitialDisplayName, r.DeviceID,
)
}
// handleApplicationServiceRegistration handles the registration of an
// application service's user by validating the AS from its access token and
// registering the user. Its two first parameters must be the two return values
@ -636,7 +685,7 @@ func handleApplicationServiceRegistration(
// Don't need to worry about appending to registration stages as
// application service registration is entirely separate.
return completeRegistration(
req.Context(), accountDB, deviceDB, r.Username, "", appserviceID,
req.Context(), accountDB, deviceDB, false, r.Username, "", appserviceID,
r.InhibitLogin, r.InitialDisplayName, r.DeviceID,
)
}
@ -656,7 +705,7 @@ func checkAndCompleteFlow(
if checkFlowCompleted(flow, cfg.Derived.Registration.Flows) {
// This flow was completed, registration can continue
return completeRegistration(
req.Context(), accountDB, deviceDB, r.Username, r.Password, "",
req.Context(), accountDB, deviceDB, false, r.Username, r.Password, "",
r.InhibitLogin, r.InitialDisplayName, r.DeviceID,
)
}
@ -708,10 +757,10 @@ func LegacyRegister(
return util.MessageResponse(http.StatusForbidden, "HMAC incorrect")
}
return completeRegistration(req.Context(), accountDB, deviceDB, r.Username, r.Password, "", false, nil, nil)
return completeRegistration(req.Context(), accountDB, deviceDB, false, r.Username, r.Password, "", false, nil, nil)
case authtypes.LoginTypeDummy:
// there is nothing to do
return completeRegistration(req.Context(), accountDB, deviceDB, r.Username, r.Password, "", false, nil, nil)
return completeRegistration(req.Context(), accountDB, deviceDB, false, r.Username, r.Password, "", false, nil, nil)
default:
return util.JSONResponse{
Code: http.StatusNotImplemented,
@ -759,6 +808,7 @@ func completeRegistration(
ctx context.Context,
accountDB *accounts.Database,
deviceDB *devices.Database,
isGuest bool,
username, password, appserviceID string,
inhibitLogin common.WeakBoolean,
displayName, deviceID *string,
@ -777,7 +827,7 @@ func completeRegistration(
}
}
acc, err := accountDB.CreateAccount(ctx, username, password, appserviceID)
acc, err := accountDB.CreateAccount(ctx, isGuest, username, password, appserviceID)
if err != nil {
return util.JSONResponse{
Code: http.StatusInternalServerError,
@ -791,7 +841,11 @@ func completeRegistration(
}
// Increment prometheus counter for created users
if isGuest {
amtRegUsers.Inc()
} else {
amtGuestUsers.Inc()
}
// Check whether inhibit_login option is set. If so, don't create an access
// token or a device for this user

View file

@ -69,7 +69,7 @@ func main() {
os.Exit(1)
}
account, err := accountDB.CreateAccount(context.Background(), *username, *password, "")
account, err := accountDB.CreateAccount(context.Background(), false, *username, *password, "")
if err != nil {
fmt.Println(err.Error())
os.Exit(1)

View file

@ -99,6 +99,9 @@ type Dendrite struct {
// If set disables new users from registering (except via shared
// secrets)
RegistrationDisabled bool `yaml:"registration_disabled"`
// If set disables new users from registering (except via shared
// secrets)
GuestAccessDisabled bool `yaml:"guest_access_disabled"`
} `yaml:"matrix"`
// The configuration specific to the media repostitory.

View file

@ -18,6 +18,9 @@ matrix:
- vector.im
- matrix.org
# Setting this to false will allow guest user to register on the server
guest_access_disabled: true
# The media repository config
media:
# The base path to where the media files will be stored. May be relative or absolute.

View file

@ -182,3 +182,20 @@ Regular users cannot create room aliases within the AS namespace
Deleting a non-existent alias should return a 404
Users can't delete other's aliases
Outbound federation can query room alias directory
Guest user cannot call /events globally
Guest users can join guest_access rooms
Guest user can set display names
Guest user cannot upgrade other users
Guest non-joined user cannot call /events on shared room
Guest non-joined user cannot call /events on invited room
Guest non-joined user cannot call /events on joined room
Guest non-joined user cannot call /events on default room
Guest non-joined users can get state for world_readable rooms
Guest non-joined users can get individual state for world_readable rooms
Guest non-joined users cannot room initalSync for non-world_readable rooms
Guest non-joined users can get individual state for world_readable rooms after leaving
Guest non-joined users cannot send messages to guest_access rooms if not joined
Guest users can sync from world_readable guest_access rooms if joined
Guest users can sync from default guest_access rooms if joined
Real non-joined users cannot room initalSync for non-world_readable rooms
Newly joined room is included in an incremental sync after invite