Updated sso implementation

Changed with comments from Kegsay and Half-Shot
- changed some return error codes
- moved sso url creation &validation to startup time
- added test to sytest whitelist
This commit is contained in:
Anand Vasudevan 2020-09-21 17:41:37 +05:30
parent 9dc798c5e4
commit ef21bed096
4 changed files with 43 additions and 38 deletions

View file

@ -84,10 +84,9 @@ func Login(
// TODO: is the the right way to read the body and re-add it?
body, err := ioutil.ReadAll(req.Body)
if err != nil {
// TODO: is this appropriate?
return util.JSONResponse{
Code: http.StatusMethodNotAllowed,
JSON: jsonerror.NotFound("Bad method"),
Code: http.StatusBadRequest,
JSON: jsonerror.BadJSON("Bad JSON"),
}
}
// add the body back to the request because ioutil.ReadAll consumes the body
@ -97,12 +96,20 @@ func Login(
var jsonBody map[string]interface{}
if err := json.Unmarshal([]byte(body), &jsonBody); err != nil {
return util.JSONResponse{
Code: http.StatusMethodNotAllowed,
JSON: jsonerror.NotFound("Bad method"),
Code: http.StatusBadRequest,
JSON: jsonerror.BadJSON("Bad JSON"),
}
}
loginType := jsonBody["type"].(string)
var loginType string
if val, ok := jsonBody["type"]; ok {
loginType = val.(string)
} else {
return util.JSONResponse{
Code: http.StatusBadRequest,
JSON: jsonerror.BadJSON("No 'type' parameter"),
}
}
if loginType == "m.login.password" {
return doPasswordLogin(req, accountDB, userAPI, cfg)
} else if loginType == "m.login.token" {
@ -164,7 +171,7 @@ func doTokenLogin(req *http.Request, accountDB accounts.Database, userAPI userap
// the login is successful, delete the login token before returning the access token to the client
if authResult.Code == http.StatusOK {
if err := auth.DeleteLoginToken(r.(*auth.LoginTokenRequest).Token); err != nil {
// TODO: what to do here?
util.GetLogger(req.Context()).WithError(err).Error("Could not delete login ticket from DB")
}
}
return authResult

View file

@ -1,4 +1,4 @@
// Copyright 2017 Vector Creations Ltd
// Copyright 2020 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -49,17 +49,8 @@ func SSORedirect(
// If dendrite is not configured to use SSO by the admin return bad method
if !cfg.CAS.Enabled || cfg.CAS.Server == "" {
return util.JSONResponse{
Code: http.StatusMethodNotAllowed,
JSON: jsonerror.NotFound("Bad method"),
}
}
// Try to parse the SSO URL configured to a url.URL type
ssoURL, err := url.Parse(cfg.CAS.Server)
if err != nil {
return util.JSONResponse{
Code: http.StatusInternalServerError,
JSON: jsonerror.Unknown("Failed to parse SSO URL configured: " + err.Error()),
Code: http.StatusNotImplemented,
JSON: jsonerror.NotFound("Method disabled"),
}
}
@ -75,8 +66,8 @@ func SSORedirect(
redirectURL, err := url.Parse(redirectURLStr)
if err != nil {
return util.JSONResponse{
Code: http.StatusInternalServerError,
JSON: jsonerror.Unknown("Invalid redirectURL: " + err.Error()),
Code: http.StatusBadRequest,
JSON: jsonerror.InvalidArgumentValue("Invalid redirectURL: " + err.Error()),
}
}
@ -86,10 +77,10 @@ func SSORedirect(
}
// Adding the params to the sso url
ssoURL := cfg.CAS.URL
ssoQueries := make(url.Values)
// the service url that we send to CAS is homeserver.com/_matrix/client/r0/login/sso/redirect?redirectUrl=xyz
ssoQueries.Set("service", req.RequestURI)
ssoURL.RawQuery = ssoQueries.Encode()
return util.RedirectResponse(ssoURL.String())
@ -106,23 +97,16 @@ func ssoTicket(
cfg *config.ClientAPI,
) util.JSONResponse {
// form the ticket validation URL from the config
ssoURL, err := url.Parse(cfg.CAS.Server + cfg.CAS.ValidateEndpoint)
if err != nil {
return util.JSONResponse{
Code: http.StatusInternalServerError,
JSON: jsonerror.Unknown("Failed to parse SSO URL configured: " + err.Error()),
}
}
validateURL := cfg.CAS.ValidateURL
ticket := req.FormValue("ticket")
// append required params to the CAS validate endpoint
ssoQueries := make(url.Values)
ssoQueries.Set("ticket", ticket)
ssoURL.RawQuery = ssoQueries.Encode()
validateQueries := make(url.Values)
validateQueries.Set("ticket", ticket)
validateURL.RawQuery = validateQueries.Encode()
// validate the ticket
casUsername, err := validateTicket(ssoURL.String())
casUsername, err := validateTicket(validateURL.String())
if err != nil {
// TODO: should I be logging these? What else should I log?
util.GetLogger(req.Context()).WithError(err).Error("CAS SSO ticket validation failed")
@ -182,10 +166,11 @@ func completeSSOAuth(
// if the user exists, then we pick that user, else we create a new user
account, err := accountDB.CreateAccount(req.Context(), username, "", "")
if err != nil {
// some error
if err != sqlutil.ErrUserExists {
// some error
util.GetLogger(req.Context()).WithError(err).Error("Could not create new user")
return util.JSONResponse{
Code: http.StatusUnauthorized,
Code: http.StatusInternalServerError,
JSON: jsonerror.Unknown("Could not create new user"),
}
} else {
@ -193,7 +178,7 @@ func completeSSOAuth(
account, err = accountDB.GetAccountByLocalpart(req.Context(), username)
if err != nil {
return util.JSONResponse{
Code: http.StatusUnauthorized,
Code: http.StatusInternalServerError,
JSON: jsonerror.Unknown("Could not query user"),
}
}

View file

@ -2,6 +2,8 @@ package config
import (
"fmt"
"net/url"
"path"
"time"
)
@ -77,12 +79,21 @@ type CAS struct {
Enabled bool `yaml:"cas_enabled"`
Server string `yaml:"cas_server"`
ValidateEndpoint string `yaml:"cas_validate_endpoint"`
URL *url.URL
ValidateURL *url.URL
}
func (cas *CAS) Verify(ConfigErrors *ConfigErrors) {
if cas.Enabled {
checkURL(ConfigErrors, "client_api.cas.cas_server", cas.Server)
checkNotEmpty(ConfigErrors, "client_api.cas.cas_validate_endpoint", cas.ValidateEndpoint)
var err error
cas.URL, err = url.Parse(cas.Server)
if err != nil {
ConfigErrors.Add(fmt.Sprintf("Couldn't parse %q (%q)to a URL", "client_api.cas.cas_server", cas.Server))
}
cas.ValidateURL.Path = path.Join(cas.URL.Path, cas.ValidateEndpoint)
checkURL(ConfigErrors, "client_api.cas.cas_validate_endpoint", cas.ValidateURL.String())
}
}

View file

@ -470,4 +470,6 @@ We can't peek into rooms with shared history_visibility
We can't peek into rooms with invited history_visibility
We can't peek into rooms with joined history_visibility
Local users can peek by room alias
Peeked rooms only turn up in the sync for the device who peeked them
Peeked rooms only turn up in the sync for the device who peeked them
Room state at a rejected message event is the same as its predecessor
Room state at a rejected state event is the same as its predecessor