// Copyright 2017 Vector Creations Ltd // Copyright 2017 New Vector 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. package routing import ( "context" "encoding/json" "errors" "fmt" "io" "net" "net/http" "net/url" "sort" "strconv" "strings" "sync" "time" "github.com/matrix-org/dendrite/internal" "github.com/tidwall/gjson" "github.com/matrix-org/dendrite/internal/eventutil" "github.com/matrix-org/dendrite/setup/config" "github.com/matrix-org/gomatrixserverlib" "github.com/matrix-org/gomatrixserverlib/spec" "github.com/matrix-org/gomatrixserverlib/tokens" "github.com/matrix-org/util" "github.com/prometheus/client_golang/prometheus" log "github.com/sirupsen/logrus" "github.com/matrix-org/dendrite/clientapi/auth" "github.com/matrix-org/dendrite/clientapi/auth/authtypes" "github.com/matrix-org/dendrite/clientapi/httputil" "github.com/matrix-org/dendrite/clientapi/userutil" userapi "github.com/matrix-org/dendrite/userapi/api" ) var ( // Prometheus metrics amtRegUsers = prometheus.NewCounter( prometheus.CounterOpts{ Name: "dendrite_clientapi_reg_users_total", Help: "Total number of registered users", }, ) ) const sessionIDLength = 24 // sessionsDict keeps track of completed auth stages for each session. // It shouldn't be passed by value because it contains a mutex. type sessionsDict struct { sync.RWMutex sessions map[string][]authtypes.LoginType sessionCompletedResult map[string]registerResponse params map[string]registerRequest timer map[string]*time.Timer // deleteSessionToDeviceID protects requests to DELETE /devices/{deviceID} from being abused. // If a UIA session is started by trying to delete device1, and then UIA is completed by deleting device2, // the delete request will fail for device2 since the UIA was initiated by trying to delete device1. deleteSessionToDeviceID map[string]string } // defaultTimeout is the timeout used to clean up sessions const defaultTimeOut = time.Minute * 5 // getCompletedStages returns the completed stages for a session. func (d *sessionsDict) getCompletedStages(sessionID string) []authtypes.LoginType { d.RLock() defer d.RUnlock() if completedStages, ok := d.sessions[sessionID]; ok { return completedStages } // Ensure that a empty slice is returned and not nil. See #399. return make([]authtypes.LoginType, 0) } // addParams adds a registerRequest to a sessionID and starts a timer to delete that registerRequest func (d *sessionsDict) addParams(sessionID string, r registerRequest) { d.startTimer(defaultTimeOut, sessionID) d.Lock() defer d.Unlock() d.params[sessionID] = r } func (d *sessionsDict) getParams(sessionID string) (registerRequest, bool) { d.RLock() defer d.RUnlock() r, ok := d.params[sessionID] return r, ok } // deleteSession cleans up a given session, either because the registration completed // successfully, or because a given timeout (default: 5min) was reached. func (d *sessionsDict) deleteSession(sessionID string) { d.Lock() defer d.Unlock() delete(d.params, sessionID) delete(d.sessions, sessionID) delete(d.deleteSessionToDeviceID, sessionID) delete(d.sessionCompletedResult, sessionID) // stop the timer, e.g. because the registration was completed if t, ok := d.timer[sessionID]; ok { if !t.Stop() { select { case <-t.C: default: } } delete(d.timer, sessionID) } } func newSessionsDict() *sessionsDict { return &sessionsDict{ sessions: make(map[string][]authtypes.LoginType), sessionCompletedResult: make(map[string]registerResponse), params: make(map[string]registerRequest), timer: make(map[string]*time.Timer), deleteSessionToDeviceID: make(map[string]string), } } func (d *sessionsDict) startTimer(duration time.Duration, sessionID string) { d.Lock() defer d.Unlock() t, ok := d.timer[sessionID] if ok { if !t.Stop() { <-t.C } t.Reset(duration) return } d.timer[sessionID] = time.AfterFunc(duration, func() { d.deleteSession(sessionID) }) } // addCompletedSessionStage records that a session has completed an auth stage // also starts a timer to delete the session once done. func (d *sessionsDict) addCompletedSessionStage(sessionID string, stage authtypes.LoginType) { d.startTimer(defaultTimeOut, sessionID) d.Lock() defer d.Unlock() for _, completedStage := range d.sessions[sessionID] { if completedStage == stage { return } } d.sessions[sessionID] = append(d.sessions[sessionID], stage) } func (d *sessionsDict) addDeviceToDelete(sessionID, deviceID string) { d.startTimer(defaultTimeOut, sessionID) d.Lock() defer d.Unlock() d.deleteSessionToDeviceID[sessionID] = deviceID } func (d *sessionsDict) addCompletedRegistration(sessionID string, response registerResponse) { d.Lock() defer d.Unlock() d.sessionCompletedResult[sessionID] = response } func (d *sessionsDict) getCompletedRegistration(sessionID string) (registerResponse, bool) { d.RLock() defer d.RUnlock() result, ok := d.sessionCompletedResult[sessionID] return result, ok } func (d *sessionsDict) getDeviceToDelete(sessionID string) (string, bool) { d.RLock() defer d.RUnlock() deviceID, ok := d.deleteSessionToDeviceID[sessionID] return deviceID, ok } var ( sessions = newSessionsDict() ) // registerRequest represents the submitted registration request. // It can be broken down into 2 sections: the auth dictionary and registration parameters. // Registration parameters vary depending on the request, and will need to remembered across // sessions. If no parameters are supplied, the server should use the parameters previously // remembered. If ANY parameters are supplied, the server should REPLACE all knowledge of // previous parameters with the ones supplied. This mean you cannot "build up" request params. type registerRequest struct { // registration parameters Password string `json:"password"` Username string `json:"username"` ServerName spec.ServerName `json:"-"` Admin bool `json:"admin"` // user-interactive auth params Auth authDict `json:"auth"` // Both DeviceID and InitialDisplayName can be omitted, or empty strings ("") // Thus a pointer is needed to differentiate between the two InitialDisplayName *string `json:"initial_device_display_name"` DeviceID *string `json:"device_id"` // Prevent this user from logging in InhibitLogin eventutil.WeakBoolean `json:"inhibit_login"` // Application Services place Type in the root of their registration // request, whereas clients place it in the authDict struct. Type authtypes.LoginType `json:"type"` } type authDict struct { Type authtypes.LoginType `json:"type"` Session string `json:"session"` Mac gomatrixserverlib.HexString `json:"mac"` // Recaptcha Response string `json:"response"` // TODO: Lots of custom keys depending on the type } // https://spec.matrix.org/v1.7/client-server-api/#user-interactive-authentication-api type userInteractiveResponse struct { Flows []authtypes.Flow `json:"flows"` Completed []authtypes.LoginType `json:"completed"` Params map[string]interface{} `json:"params"` Session string `json:"session"` } // newUserInteractiveResponse will return a struct to be sent back to the client // during registration. func newUserInteractiveResponse( sessionID string, fs []authtypes.Flow, params map[string]interface{}, ) userInteractiveResponse { return userInteractiveResponse{ fs, sessions.getCompletedStages(sessionID), params, sessionID, } } // https://spec.matrix.org/v1.7/client-server-api/#post_matrixclientv3register type registerResponse struct { UserID string `json:"user_id"` AccessToken string `json:"access_token,omitempty"` DeviceID string `json:"device_id,omitempty"` } // recaptchaResponse represents the HTTP response from a Google Recaptcha server type recaptchaResponse struct { Success bool `json:"success"` ChallengeTS time.Time `json:"challenge_ts"` Hostname string `json:"hostname"` ErrorCodes []int `json:"error-codes"` } var ( ErrInvalidCaptcha = errors.New("invalid captcha response") ErrMissingResponse = errors.New("captcha response is required") ErrCaptchaDisabled = errors.New("captcha registration is disabled") ) // validateRecaptcha returns an error response if the captcha response is invalid func validateRecaptcha( cfg *config.ClientAPI, response string, clientip string, ) error { ip, _, _ := net.SplitHostPort(clientip) if !cfg.RecaptchaEnabled { return ErrCaptchaDisabled } if response == "" { return ErrMissingResponse } // Make a POST request to the captcha provider API to check the captcha response resp, err := http.PostForm(cfg.RecaptchaSiteVerifyAPI, url.Values{ "secret": {cfg.RecaptchaPrivateKey}, "response": {response}, "remoteip": {ip}, }, ) if err != nil { return err } // Close the request once we're finishing reading from it defer resp.Body.Close() // nolint: errcheck // Grab the body of the response from the captcha server var r recaptchaResponse body, err := io.ReadAll(resp.Body) if err != nil { return err } err = json.Unmarshal(body, &r) if err != nil { return err } // Check that we received a "success" if !r.Success { return ErrInvalidCaptcha } return nil } // UserIDIsWithinApplicationServiceNamespace checks to see if a given userID // falls within any of the namespaces of a given Application Service. If no // Application Service is given, it will check to see if it matches any // Application Service's namespace. func UserIDIsWithinApplicationServiceNamespace( cfg *config.ClientAPI, userID string, appservice *config.ApplicationService, ) bool { var local, domain, err = gomatrixserverlib.SplitID('@', userID) if err != nil { // Not a valid userID return false } if !cfg.Matrix.IsLocalServerName(domain) { return false } if appservice != nil { if appservice.SenderLocalpart == local { return true } // Loop through given application service's namespaces and see if any match for _, namespace := range appservice.NamespaceMap["users"] { // AS namespaces are checked for validity in config if namespace.RegexpObject.MatchString(userID) { return true } } return false } // Loop through all known application service's namespaces and see if any match for _, knownAppService := range cfg.Derived.ApplicationServices { if knownAppService.SenderLocalpart == local { return true } for _, namespace := range knownAppService.NamespaceMap["users"] { // AS namespaces are checked for validity in config if namespace.RegexpObject.MatchString(userID) { return true } } } return false } // UsernameMatchesMultipleExclusiveNamespaces will check if a given username matches // more than one exclusive namespace. More than one is not allowed func UsernameMatchesMultipleExclusiveNamespaces( cfg *config.ClientAPI, username string, ) bool { userID := userutil.MakeUserID(username, cfg.Matrix.ServerName) // Check namespaces and see if more than one match matchCount := 0 for _, appservice := range cfg.Derived.ApplicationServices { if appservice.OwnsNamespaceCoveringUserId(userID) { if matchCount++; matchCount > 1 { return true } } } return false } // UsernameMatchesExclusiveNamespaces will check if a given username matches any // application service's exclusive users namespace func UsernameMatchesExclusiveNamespaces( cfg *config.ClientAPI, username string, ) bool { userID := userutil.MakeUserID(username, cfg.Matrix.ServerName) return cfg.Derived.ExclusiveApplicationServicesUsernameRegexp.MatchString(userID) } // validateApplicationService checks if a provided application service token // corresponds to one that is registered. If so, then it checks if the desired // username is within that application service's namespace. As long as these // two requirements are met, no error will be returned. func validateApplicationService( cfg *config.ClientAPI, username string, accessToken string, ) (string, *util.JSONResponse) { // Check if the token if the application service is valid with one we have // registered in the config. var matchedApplicationService *config.ApplicationService for _, appservice := range cfg.Derived.ApplicationServices { if appservice.ASToken == accessToken { matchedApplicationService = &appservice break } } if matchedApplicationService == nil { return "", &util.JSONResponse{ Code: http.StatusUnauthorized, JSON: spec.UnknownToken("Supplied access_token does not match any known application service"), } } userID := userutil.MakeUserID(username, cfg.Matrix.ServerName) // Ensure the desired username is within at least one of the application service's namespaces. if !UserIDIsWithinApplicationServiceNamespace(cfg, userID, matchedApplicationService) { // If we didn't find any matches, return M_EXCLUSIVE return "", &util.JSONResponse{ Code: http.StatusBadRequest, JSON: spec.ASExclusive(fmt.Sprintf( "Supplied username %s did not match any namespaces for application service ID: %s", username, matchedApplicationService.ID)), } } // Check this user does not fit multiple application service namespaces if UsernameMatchesMultipleExclusiveNamespaces(cfg, userID) { return "", &util.JSONResponse{ Code: http.StatusBadRequest, JSON: spec.ASExclusive(fmt.Sprintf( "Supplied username %s matches multiple exclusive application service namespaces. Only 1 match allowed", username)), } } // Check username application service is trying to register is valid if err := internal.ValidateApplicationServiceUsername(username, cfg.Matrix.ServerName); err != nil { return "", internal.UsernameResponse(err) } // No errors, registration valid return matchedApplicationService.ID, nil } // Register processes a /register request. // https://spec.matrix.org/v1.7/client-server-api/#post_matrixclientv3register func Register( req *http.Request, userAPI userapi.ClientUserAPI, cfg *config.ClientAPI, ) util.JSONResponse { defer req.Body.Close() // nolint: errcheck reqBody, err := io.ReadAll(req.Body) if err != nil { return util.JSONResponse{ Code: http.StatusBadRequest, JSON: spec.NotJSON("Unable to read request body"), } } var r registerRequest host := spec.ServerName(req.Host) if v := cfg.Matrix.VirtualHostForHTTPHost(host); v != nil { r.ServerName = v.ServerName } else { r.ServerName = cfg.Matrix.ServerName } sessionID := gjson.GetBytes(reqBody, "auth.session").String() if sessionID == "" { // Generate a new, random session ID sessionID = util.RandomString(sessionIDLength) } else if data, ok := sessions.getParams(sessionID); ok { // Use the parameters from the session as our defaults. // Some of these might end up being overwritten if the // values are specified again in the request body. r.Username = data.Username r.ServerName = data.ServerName r.Password = data.Password r.DeviceID = data.DeviceID r.InitialDisplayName = data.InitialDisplayName r.InhibitLogin = data.InhibitLogin // Check if the user already registered using this session, if so, return that result if response, ok := sessions.getCompletedRegistration(sessionID); ok { return util.JSONResponse{ Code: http.StatusOK, JSON: response, } } } if resErr := httputil.UnmarshalJSON(reqBody, &r); resErr != nil { return *resErr } if req.URL.Query().Get("kind") == "guest" { return handleGuestRegistration(req, r, cfg, userAPI) } // Don't allow numeric usernames less than MAX_INT64. if _, err = strconv.ParseInt(r.Username, 10, 64); err == nil { return util.JSONResponse{ Code: http.StatusBadRequest, JSON: spec.InvalidUsername("Numeric user IDs are reserved"), } } // Auto generate a numeric username if r.Username is empty if r.Username == "" { nreq := &userapi.QueryNumericLocalpartRequest{ ServerName: r.ServerName, } nres := &userapi.QueryNumericLocalpartResponse{} if err = userAPI.QueryNumericLocalpart(req.Context(), nreq, nres); err != nil { util.GetLogger(req.Context()).WithError(err).Error("userAPI.QueryNumericLocalpart failed") return util.JSONResponse{ Code: http.StatusInternalServerError, JSON: spec.InternalServerError{}, } } r.Username = strconv.FormatInt(nres.ID, 10) } // Is this an appservice registration? It will be if the access // token is supplied accessToken, accessTokenErr := auth.ExtractAccessToken(req) // Squash username to all lowercase letters r.Username = strings.ToLower(r.Username) switch { case r.Type == authtypes.LoginTypeApplicationService && accessTokenErr == nil: // Spec-compliant case (the access_token is specified and the login type // is correctly set, so it's an appservice registration) if err = internal.ValidateApplicationServiceUsername(r.Username, r.ServerName); err != nil { return *internal.UsernameResponse(err) } case accessTokenErr == nil: // Non-spec-compliant case (the access_token is specified but the login // type is not known or specified) return util.JSONResponse{ Code: http.StatusBadRequest, JSON: spec.MissingParam("A known registration type (e.g. m.login.application_service) must be specified if an access_token is provided"), } default: // Spec-compliant case (neither the access_token nor the login type are // specified, so it's a normal user registration) if err = internal.ValidateUsername(r.Username, r.ServerName); err != nil { return *internal.UsernameResponse(err) } } if err = internal.ValidatePassword(r.Password); err != nil { return *internal.PasswordResponse(err) } logger := util.GetLogger(req.Context()) logger.WithFields(log.Fields{ "username": r.Username, "auth.type": r.Auth.Type, "session_id": r.Auth.Session, }).Info("Processing registration request") return handleRegistrationFlow(req, r, sessionID, cfg, userAPI, accessToken, accessTokenErr) } func handleGuestRegistration( req *http.Request, r registerRequest, cfg *config.ClientAPI, userAPI userapi.ClientUserAPI, ) util.JSONResponse { registrationEnabled := !cfg.RegistrationDisabled guestsEnabled := !cfg.GuestsDisabled if v := cfg.Matrix.VirtualHost(r.ServerName); v != nil { registrationEnabled, guestsEnabled = v.RegistrationAllowed() } if !registrationEnabled || !guestsEnabled { return util.JSONResponse{ Code: http.StatusForbidden, JSON: spec.Forbidden( fmt.Sprintf("Guest registration is disabled on %q", r.ServerName), ), } } var res userapi.PerformAccountCreationResponse err := userAPI.PerformAccountCreation(req.Context(), &userapi.PerformAccountCreationRequest{ AccountType: userapi.AccountTypeGuest, ServerName: r.ServerName, }, &res) if err != nil { return util.JSONResponse{ Code: http.StatusInternalServerError, JSON: spec.Unknown("failed to create account: " + err.Error()), } } token, err := tokens.GenerateLoginToken(tokens.TokenOptions{ ServerPrivateKey: cfg.Matrix.PrivateKey.Seed(), ServerName: string(res.Account.ServerName), UserID: res.Account.UserID, }) if err != nil { return util.JSONResponse{ Code: http.StatusInternalServerError, JSON: spec.Unknown("Failed to generate access token"), } } //we don't allow guests to specify their own device_id var devRes userapi.PerformDeviceCreationResponse err = userAPI.PerformDeviceCreation(req.Context(), &userapi.PerformDeviceCreationRequest{ Localpart: res.Account.Localpart, ServerName: res.Account.ServerName, DeviceDisplayName: r.InitialDisplayName, AccessToken: token, IPAddr: req.RemoteAddr, UserAgent: req.UserAgent(), FromRegistration: true, }, &devRes) if err != nil { return util.JSONResponse{ Code: http.StatusInternalServerError, JSON: spec.Unknown("failed to create device: " + err.Error()), } } return util.JSONResponse{ Code: http.StatusOK, JSON: registerResponse{ UserID: devRes.Device.UserID, AccessToken: devRes.Device.AccessToken, DeviceID: devRes.Device.ID, }, } } // localpartMatchesExclusiveNamespaces will check if a given username matches any // application service's exclusive users namespace func localpartMatchesExclusiveNamespaces( cfg *config.ClientAPI, localpart string, ) bool { userID := userutil.MakeUserID(localpart, cfg.Matrix.ServerName) return cfg.Derived.ExclusiveApplicationServicesUsernameRegexp.MatchString(userID) } // handleRegistrationFlow will direct and complete registration flow stages // that the client has requested. // nolint: gocyclo func handleRegistrationFlow( req *http.Request, r registerRequest, sessionID string, cfg *config.ClientAPI, userAPI userapi.ClientUserAPI, accessToken string, accessTokenErr error, ) util.JSONResponse { // TODO: Enable registration config flag // TODO: Guest account upgrading // TODO: Handle loading of previous session parameters from database. // TODO: Handle mapping registrationRequest parameters into session parameters // TODO: email / msisdn auth types. // Appservices are special and are not affected by disabled // registration or user exclusivity. We'll go onto the appservice // registration flow if a valid access token was provided or if // the login type specifically requests it. if r.Type == authtypes.LoginTypeApplicationService && accessTokenErr == nil { return handleApplicationServiceRegistration( accessToken, accessTokenErr, req, r, cfg, userAPI, ) } registrationEnabled := !cfg.RegistrationDisabled if v := cfg.Matrix.VirtualHost(r.ServerName); v != nil { registrationEnabled, _ = v.RegistrationAllowed() } if !registrationEnabled && r.Auth.Type != authtypes.LoginTypeSharedSecret { return util.JSONResponse{ Code: http.StatusForbidden, JSON: spec.Forbidden( fmt.Sprintf("Registration is disabled on %q", r.ServerName), ), } } // Make sure normal user isn't registering under an exclusive application // service namespace. Skip this check if no app services are registered. // If an access token is provided, ignore this check this is an appservice // request and we will validate in validateApplicationService if len(cfg.Derived.ApplicationServices) != 0 && localpartMatchesExclusiveNamespaces(cfg, r.Username) { return util.JSONResponse{ Code: http.StatusBadRequest, JSON: spec.ASExclusive("This username is reserved by an application service."), } } switch r.Auth.Type { case authtypes.LoginTypeRecaptcha: // Check given captcha response err := validateRecaptcha(cfg, r.Auth.Response, req.RemoteAddr) switch err { case ErrCaptchaDisabled: return util.JSONResponse{Code: http.StatusForbidden, JSON: spec.Unknown(err.Error())} case ErrMissingResponse: return util.JSONResponse{Code: http.StatusBadRequest, JSON: spec.BadJSON(err.Error())} case ErrInvalidCaptcha: return util.JSONResponse{Code: http.StatusUnauthorized, JSON: spec.BadJSON(err.Error())} case nil: default: util.GetLogger(req.Context()).WithError(err).Error("failed to validate recaptcha") return util.JSONResponse{Code: http.StatusInternalServerError, JSON: spec.InternalServerError{}} } // Add Recaptcha to the list of completed registration stages sessions.addCompletedSessionStage(sessionID, authtypes.LoginTypeRecaptcha) case authtypes.LoginTypeDummy: // there is nothing to do // Add Dummy to the list of completed registration stages sessions.addCompletedSessionStage(sessionID, authtypes.LoginTypeDummy) case "": // An empty auth type means that we want to fetch the available // flows. It can also mean that we want to register as an appservice // but that is handed above. default: return util.JSONResponse{ Code: http.StatusNotImplemented, JSON: spec.Unknown("unknown/unimplemented auth type"), } } // Check if the user's registration flow has been completed successfully // A response with current registration flow and remaining available methods // will be returned if a flow has not been successfully completed yet return checkAndCompleteFlow(sessions.getCompletedStages(sessionID), req, r, sessionID, cfg, userAPI) } // 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 // of the auth.ExtractAccessToken function. // Returns an error if the access token couldn't be extracted from the request // at an earlier step of the registration workflow, or if the provided access // token doesn't belong to a valid AS, or if there was an issue completing the // registration process. func handleApplicationServiceRegistration( accessToken string, tokenErr error, req *http.Request, r registerRequest, cfg *config.ClientAPI, userAPI userapi.ClientUserAPI, ) util.JSONResponse { // Check if we previously had issues extracting the access token from the // request. if tokenErr != nil { return util.JSONResponse{ Code: http.StatusUnauthorized, JSON: spec.MissingToken(tokenErr.Error()), } } // Check application service register user request is valid. // The application service's ID is returned if so. appserviceID, err := internal.ValidateApplicationServiceRequest( cfg, r.Username, accessToken, ) if err != nil { return *err } // If no error, application service was successfully validated. // Don't need to worry about appending to registration stages as // application service registration is entirely separate. return completeRegistration( req.Context(), userAPI, r.Username, r.ServerName, "", "", appserviceID, req.RemoteAddr, req.UserAgent(), r.Auth.Session, r.InhibitLogin, r.InitialDisplayName, r.DeviceID, userapi.AccountTypeAppService, ) } // checkAndCompleteFlow checks if a given registration flow is completed given // a set of allowed flows. If so, registration is completed, otherwise a // response with func checkAndCompleteFlow( flow []authtypes.LoginType, req *http.Request, r registerRequest, sessionID string, cfg *config.ClientAPI, userAPI userapi.ClientUserAPI, ) util.JSONResponse { if checkFlowCompleted(flow, cfg.Derived.Registration.Flows) { // This flow was completed, registration can continue return completeRegistration( req.Context(), userAPI, r.Username, r.ServerName, "", r.Password, "", req.RemoteAddr, req.UserAgent(), sessionID, r.InhibitLogin, r.InitialDisplayName, r.DeviceID, userapi.AccountTypeUser, ) } sessions.addParams(sessionID, r) // There are still more stages to complete. // Return the flows and those that have been completed. return util.JSONResponse{ Code: http.StatusUnauthorized, JSON: newUserInteractiveResponse(sessionID, cfg.Derived.Registration.Flows, cfg.Derived.Registration.Params), } } // completeRegistration runs some rudimentary checks against the submitted // input, then if successful creates an account and a newly associated device // We pass in each individual part of the request here instead of just passing a // registerRequest, as this function serves requests encoded as both // registerRequests and legacyRegisterRequests, which share some attributes but // not all func completeRegistration( ctx context.Context, userAPI userapi.ClientUserAPI, username string, serverName spec.ServerName, displayName string, password, appserviceID, ipAddr, userAgent, sessionID string, inhibitLogin eventutil.WeakBoolean, deviceDisplayName, deviceID *string, accType userapi.AccountType, ) util.JSONResponse { if username == "" { return util.JSONResponse{ Code: http.StatusBadRequest, JSON: spec.MissingParam("Missing username"), } } // Blank passwords are only allowed by registered application services if password == "" && appserviceID == "" { return util.JSONResponse{ Code: http.StatusBadRequest, JSON: spec.MissingParam("Missing password"), } } var accRes userapi.PerformAccountCreationResponse err := userAPI.PerformAccountCreation(ctx, &userapi.PerformAccountCreationRequest{ AppServiceID: appserviceID, Localpart: username, ServerName: serverName, Password: password, AccountType: accType, OnConflict: userapi.ConflictAbort, }, &accRes) if err != nil { if _, ok := err.(*userapi.ErrorConflict); ok { // user already exists return util.JSONResponse{ Code: http.StatusBadRequest, JSON: spec.UserInUse("Desired user ID is already taken."), } } return util.JSONResponse{ Code: http.StatusInternalServerError, JSON: spec.Unknown("failed to create account: " + err.Error()), } } // Increment prometheus counter for created users amtRegUsers.Inc() // Check whether inhibit_login option is set. If so, don't create an access // token or a device for this user if inhibitLogin { return util.JSONResponse{ Code: http.StatusOK, JSON: registerResponse{ UserID: userutil.MakeUserID(username, accRes.Account.ServerName), }, } } token, err := auth.GenerateAccessToken() if err != nil { return util.JSONResponse{ Code: http.StatusInternalServerError, JSON: spec.Unknown("Failed to generate access token"), } } if displayName != "" { _, _, err = userAPI.SetDisplayName(ctx, username, serverName, displayName) if err != nil { return util.JSONResponse{ Code: http.StatusInternalServerError, JSON: spec.Unknown("failed to set display name: " + err.Error()), } } } var devRes userapi.PerformDeviceCreationResponse err = userAPI.PerformDeviceCreation(ctx, &userapi.PerformDeviceCreationRequest{ Localpart: username, ServerName: serverName, AccessToken: token, DeviceDisplayName: deviceDisplayName, DeviceID: deviceID, IPAddr: ipAddr, UserAgent: userAgent, FromRegistration: true, }, &devRes) if err != nil { return util.JSONResponse{ Code: http.StatusInternalServerError, JSON: spec.Unknown("failed to create device: " + err.Error()), } } result := registerResponse{ UserID: devRes.Device.UserID, AccessToken: devRes.Device.AccessToken, DeviceID: devRes.Device.ID, } sessions.addCompletedRegistration(sessionID, result) return util.JSONResponse{ Code: http.StatusOK, JSON: result, } } // checkFlows checks a single completed flow against another required one. If // one contains at least all of the stages that the other does, checkFlows // returns true. func checkFlows( completedStages []authtypes.LoginType, requiredStages []authtypes.LoginType, ) bool { // Create temporary slices so they originals will not be modified on sorting completed := make([]authtypes.LoginType, len(completedStages)) required := make([]authtypes.LoginType, len(requiredStages)) copy(completed, completedStages) copy(required, requiredStages) // Sort the slices for simple comparison sort.Slice(completed, func(i, j int) bool { return completed[i] < completed[j] }) sort.Slice(required, func(i, j int) bool { return required[i] < required[j] }) // Iterate through each slice, going to the next required slice only once // we've found a match. i, j := 0, 0 for j < len(required) { // Exit if we've reached the end of our input without being able to // match all of the required stages. if i >= len(completed) { return false } // If we've found a stage we want, move on to the next required stage. if completed[i] == required[j] { j++ } i++ } return true } // checkFlowCompleted checks if a registration flow complies with any allowed flow // dictated by the server. Order of stages does not matter. A user may complete // extra stages as long as the required stages of at least one flow is met. func checkFlowCompleted( flow []authtypes.LoginType, allowedFlows []authtypes.Flow, ) bool { // Iterate through possible flows to check whether any have been fully completed. for _, allowedFlow := range allowedFlows { if checkFlows(flow, allowedFlow.Stages) { return true } } return false } type availableResponse struct { Available bool `json:"available"` } // RegisterAvailable checks if the username is already taken or invalid. func RegisterAvailable( req *http.Request, cfg *config.ClientAPI, registerAPI userapi.ClientUserAPI, ) util.JSONResponse { username := req.URL.Query().Get("username") // Squash username to all lowercase letters username = strings.ToLower(username) domain := cfg.Matrix.ServerName host := spec.ServerName(req.Host) if v := cfg.Matrix.VirtualHostForHTTPHost(host); v != nil { domain = v.ServerName } if u, l, err := cfg.Matrix.SplitLocalID('@', username); err == nil { username, domain = u, l } for _, v := range cfg.Matrix.VirtualHosts { if v.ServerName == domain && !v.AllowRegistration { return util.JSONResponse{ Code: http.StatusForbidden, JSON: spec.Forbidden( fmt.Sprintf("Registration is not allowed on %q", string(v.ServerName)), ), } } } if err := internal.ValidateUsername(username, domain); err != nil { return *internal.UsernameResponse(err) } // Check if this username is reserved by an application service userID := userutil.MakeUserID(username, domain) for _, appservice := range cfg.Derived.ApplicationServices { if appservice.OwnsNamespaceCoveringUserId(userID) { return util.JSONResponse{ Code: http.StatusBadRequest, JSON: spec.UserInUse("Desired user ID is reserved by an application service."), } } } res := &userapi.QueryAccountAvailabilityResponse{} err := registerAPI.QueryAccountAvailability(req.Context(), &userapi.QueryAccountAvailabilityRequest{ Localpart: username, ServerName: domain, }, res) if err != nil { return util.JSONResponse{ Code: http.StatusInternalServerError, JSON: spec.Unknown("failed to check availability:" + err.Error()), } } if !res.Available { return util.JSONResponse{ Code: http.StatusBadRequest, JSON: spec.UserInUse("Desired User ID is already taken."), } } return util.JSONResponse{ Code: http.StatusOK, JSON: availableResponse{ Available: true, }, } } func handleSharedSecretRegistration(cfg *config.ClientAPI, userAPI userapi.ClientUserAPI, sr *SharedSecretRegistration, req *http.Request) util.JSONResponse { ssrr, err := NewSharedSecretRegistrationRequest(req.Body) if err != nil { return util.JSONResponse{ Code: 400, JSON: spec.BadJSON(fmt.Sprintf("malformed json: %s", err)), } } valid, err := sr.IsValidMacLogin(ssrr.Nonce, ssrr.User, ssrr.Password, ssrr.Admin, ssrr.MacBytes) if err != nil { return util.ErrorResponse(err) } if !valid { return util.JSONResponse{ Code: 403, JSON: spec.Forbidden("bad mac"), } } // downcase capitals ssrr.User = strings.ToLower(ssrr.User) if err = internal.ValidateUsername(ssrr.User, cfg.Matrix.ServerName); err != nil { return *internal.UsernameResponse(err) } if err = internal.ValidatePassword(ssrr.Password); err != nil { return *internal.PasswordResponse(err) } deviceID := "shared_secret_registration" accType := userapi.AccountTypeUser if ssrr.Admin { accType = userapi.AccountTypeAdmin } return completeRegistration(req.Context(), userAPI, ssrr.User, cfg.Matrix.ServerName, ssrr.DisplayName, ssrr.Password, "", req.RemoteAddr, req.UserAgent(), "", false, &ssrr.User, &deviceID, accType) }