Fix interactive registration failing (#583)

* Fix interactive registration failing because of being confused with AS registration

* Fix AS registration tests

* Move AS registration handling to dedicated function and split the switch/case to avoid unnecessary condition

* Ignore handleRegistrationFlow() for gocyclo and add some doc/comments on the code
main
Brendan Abolivier 2018-11-06 14:40:37 +00:00 committed by GitHub
parent 1a82e6bc58
commit 8ff136e595
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 72 additions and 49 deletions

View File

@ -369,18 +369,11 @@ func UsernameMatchesExclusiveNamespaces(
// two requirements are met, no error will be returned. // two requirements are met, no error will be returned.
func validateApplicationService( func validateApplicationService(
cfg *config.Dendrite, cfg *config.Dendrite,
req *http.Request,
username string, username string,
accessToken string,
) (string, *util.JSONResponse) { ) (string, *util.JSONResponse) {
// Check if the token if the application service is valid with one we have // Check if the token if the application service is valid with one we have
// registered in the config. // registered in the config.
accessToken, err := auth.ExtractAccessToken(req)
if err != nil {
return "", &util.JSONResponse{
Code: http.StatusUnauthorized,
JSON: jsonerror.MissingToken(err.Error()),
}
}
var matchedApplicationService *config.ApplicationService var matchedApplicationService *config.ApplicationService
for _, appservice := range cfg.Derived.ApplicationServices { for _, appservice := range cfg.Derived.ApplicationServices {
if appservice.ASToken == accessToken { if appservice.ASToken == accessToken {
@ -497,6 +490,7 @@ func Register(
// handleRegistrationFlow will direct and complete registration flow stages // handleRegistrationFlow will direct and complete registration flow stages
// that the client has requested. // that the client has requested.
// nolint: gocyclo
func handleRegistrationFlow( func handleRegistrationFlow(
req *http.Request, req *http.Request,
r registerRequest, r registerRequest,
@ -542,21 +536,29 @@ func handleRegistrationFlow(
// Add SharedSecret to the list of completed registration stages // Add SharedSecret to the list of completed registration stages
sessions.AddCompletedStage(sessionID, authtypes.LoginTypeSharedSecret) sessions.AddCompletedStage(sessionID, authtypes.LoginTypeSharedSecret)
case "", authtypes.LoginTypeApplicationService: case "":
// not passing a Auth.Type is allowed for ApplicationServices. So assume that as well // Extract the access token from the request, if there's one to extract
// Check application service register user request is valid. // (which we can know by checking whether the error is nil or not).
// The application service's ID is returned if so. accessToken, err := auth.ExtractAccessToken(req)
appserviceID, err := validateApplicationService(cfg, req, r.Username)
if err != nil { // A missing auth type can mean either the registration is performed by
return *err // an AS or the request is made as the first step of a registration
// using the User-Interactive Authentication API. This can be determined
// by whether the request contains an access token.
if err == nil {
return handleApplicationServiceRegistration(
accessToken, err, req, r, cfg, accountDB, deviceDB,
)
} }
// If no error, application service was successfully validated. case authtypes.LoginTypeApplicationService:
// Don't need to worry about appending to registration stages as // Extract the access token from the request.
// application service registration is entirely separate. accessToken, err := auth.ExtractAccessToken(req)
return completeRegistration( // Let the AS registration handler handle the process from here. We
req.Context(), accountDB, deviceDB, r.Username, "", appserviceID, // don't need a condition on that call since the registration is clearly
r.InhibitLogin, r.InitialDisplayName, // stated as being AS-related.
return handleApplicationServiceRegistration(
accessToken, err, req, r, cfg, accountDB, deviceDB,
) )
case authtypes.LoginTypeDummy: case authtypes.LoginTypeDummy:
@ -578,6 +580,50 @@ func handleRegistrationFlow(
req, r, sessionID, cfg, accountDB, deviceDB) req, r, sessionID, cfg, accountDB, deviceDB)
} }
// 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.Dendrite,
accountDB *accounts.Database,
deviceDB *devices.Database,
) 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: jsonerror.MissingToken(tokenErr.Error()),
}
}
// Check application service register user request is valid.
// The application service's ID is returned if so.
appserviceID, err := validateApplicationService(
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(), accountDB, deviceDB, r.Username, "", appserviceID,
r.InhibitLogin, r.InitialDisplayName,
)
}
// checkAndCompleteFlow checks if a given registration flow is completed given // checkAndCompleteFlow checks if a given registration flow is completed given
// a set of allowed flows. If so, registration is completed, otherwise a // a set of allowed flows. If so, registration is completed, otherwise a
// response with // response with

View File

@ -15,8 +15,6 @@
package routing package routing
import ( import (
"net/http"
"net/url"
"regexp" "regexp"
"testing" "testing"
@ -186,47 +184,26 @@ func TestValidationOfApplicationServices(t *testing.T) {
fakeConfig.Derived.ApplicationServices = []config.ApplicationService{fakeApplicationService} fakeConfig.Derived.ApplicationServices = []config.ApplicationService{fakeApplicationService}
// Access token is correct, user_id omitted so we are acting as SenderLocalpart // Access token is correct, user_id omitted so we are acting as SenderLocalpart
URL, _ := url.Parse("http://localhost/register?access_token=1234") asID, resp := validateApplicationService(&fakeConfig, fakeSenderLocalpart, "1234")
fakeHTTPRequest := http.Request{
Method: "POST",
URL: URL,
}
asID, resp := validateApplicationService(&fakeConfig, &fakeHTTPRequest, fakeSenderLocalpart)
if resp != nil || asID != fakeID { if resp != nil || asID != fakeID {
t.Errorf("appservice should have validated and returned correct ID: %s", resp.JSON) t.Errorf("appservice should have validated and returned correct ID: %s", resp.JSON)
} }
// Access token is incorrect, user_id omitted so we are acting as SenderLocalpart // Access token is incorrect, user_id omitted so we are acting as SenderLocalpart
URL, _ = url.Parse("http://localhost/register?access_token=xxxx") asID, resp = validateApplicationService(&fakeConfig, fakeSenderLocalpart, "xxxx")
fakeHTTPRequest = http.Request{
Method: "POST",
URL: URL,
}
asID, resp = validateApplicationService(&fakeConfig, &fakeHTTPRequest, fakeSenderLocalpart)
if resp == nil || asID == fakeID { if resp == nil || asID == fakeID {
t.Errorf("access_token should have been marked as invalid") t.Errorf("access_token should have been marked as invalid")
} }
// Access token is correct, acting as valid user_id // Access token is correct, acting as valid user_id
URL, _ = url.Parse("http://localhost/register?access_token=1234&user_id=@_appservice_bob:localhost") asID, resp = validateApplicationService(&fakeConfig, "_appservice_bob", "1234")
fakeHTTPRequest = http.Request{
Method: "POST",
URL: URL,
}
asID, resp = validateApplicationService(&fakeConfig, &fakeHTTPRequest, "_appservice_bob")
if resp != nil || asID != fakeID { if resp != nil || asID != fakeID {
t.Errorf("access_token and user_id should've been valid: %s", resp.JSON) t.Errorf("access_token and user_id should've been valid: %s", resp.JSON)
} }
// Access token is correct, acting as invalid user_id // Access token is correct, acting as invalid user_id
URL, _ = url.Parse("http://localhost/register?access_token=1234&user_id=@_something_else:localhost") asID, resp = validateApplicationService(&fakeConfig, "_something_else", "1234")
fakeHTTPRequest = http.Request{
Method: "POST",
URL: URL,
}
asID, resp = validateApplicationService(&fakeConfig, &fakeHTTPRequest, "_something_else")
if resp == nil || asID == fakeID { if resp == nil || asID == fakeID {
t.Errorf("user_id should not have been valid: %s", t.Errorf("user_id should not have been valid: @_something_else:localhost")
fakeHTTPRequest.URL.Query().Get("user_id"))
} }
} }