-
-
Notifications
You must be signed in to change notification settings - Fork 677
[WIP] Implement m.login.token and SSO login initial changes #1374
Conversation
In this case the
We do not have token auth yet - see #403 |
#429 for Should I work on #403 now before finishing #1297 ? Can #429 be modified and used? |
Looks like the spec recommends that we use macaroons. https://matrix.org/docs/spec/client_server/latest#id575 They also say that we can implement access tokens in any way we want. https://matrix.org/docs/spec/client_server/latest#id180 Should I go ahead and use random string tokens for the login token? |
Yes, I would use random strings for now. |
Database level changes not made
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking roughly correct, would be good to get sytests passing.
clientapi/routing/login.go
Outdated
if err != nil { | ||
// TODO: is this appropriate? | ||
return util.JSONResponse{ | ||
Code: http.StatusMethodNotAllowed, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The method is fine, it's just they didn't give an HTTP body.
clientapi/routing/login.go
Outdated
if authErr != nil { | ||
return *authErr | ||
|
||
loginType := jsonBody["type"].(string) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Won't this panic if the request body has no type
key?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes it did. I hadn't checked for that earlier
clientapi/routing/login.go
Outdated
// 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? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just log it and continue. This is just to cleanup.
clientapi/routing/sso.go
Outdated
if !cfg.CAS.Enabled || cfg.CAS.Server == "" { | ||
return util.JSONResponse{ | ||
Code: http.StatusMethodNotAllowed, | ||
JSON: jsonerror.NotFound("Bad method"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's nothing wrong with the HTTP method here, the code is wrong. Give a 501 Not Implemented or something.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right. I changed this and a few other http code returns as well to something I thought were more appropriate
clientapi/routing/sso.go
Outdated
} | ||
|
||
// Try to parse the SSO URL configured to a url.URL type | ||
ssoURL, err := url.Parse(cfg.CAS.Server) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could do this on startup rather than at request time.
clientapi/routing/sso.go
Outdated
redirectURL, err := url.Parse(redirectURLStr) | ||
if err != nil { | ||
return util.JSONResponse{ | ||
Code: http.StatusInternalServerError, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't a server error if we fail to parse the URL, it's the client who supplies the URL. Perhaps a 400 Bad Request would be more appropriate.
clientapi/routing/sso.go
Outdated
cfg *config.ClientAPI, | ||
) util.JSONResponse { | ||
// form the ticket validation URL from the config | ||
ssoURL, err := url.Parse(cfg.CAS.Server + cfg.CAS.ValidateEndpoint) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be done at startup.
Made another commit with some of the changes from the comments. I need some help with making database changes in |
I changed |
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
// the service url that we send to CAS is homeserver.com/_matrix/client/r0/login/sso/redirect?redirectUrl=xyz | ||
ssoQueries.Set("service", req.RequestURI) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suspect it doesn't matter too much, but it could be clearer to redirect to a different endpoint instead of using /login/sso/redirect
as both the redirect endpoint and the endpoint that resolves the ticket. I'm not sure if this is common in CAS implementations or not. Synapse uses a separate endpoint, but it is also under the /_matrix
prefix, which it should not be.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I thought about this too and in the older version of the spec (r0.4.0) there are 2 endpoints specified for CAS auth (/login/cas/redirect
and /login/cas/ticket
). But the newer spec (r0.6.1) doesn't mention the separate /ticket
endpoint, so I thought I should just go with the single endpoint.
Splitting it up into 2 endpoints can be easily done.
About it not being in the /_matrix
prefix, I don't know enough about common practices in other CAS implementations to comment. The spec does say /_matrix/client/r0/login/sso/redirect
though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I thought about this too and in the older version of the spec (r0.4.0) there are 2 endpoints specified for CAS auth (
/login/cas/redirect
and/login/cas/ticket
). But the newer spec (r0.6.1) doesn't mention the separate/ticket
endpoint, so I thought I should just go with the single endpoint.
I believe the second endpoint is meant to be implementation specific!
Splitting it up into 2 endpoints can be easily done.
I'd let @kegsay or @neilalexander decide whether this should be done or not. 😄
About it not being in the
/_matrix
prefix, I don't know enough about common practices in other CAS implementations to comment. The spec does say/_matrix/client/r0/login/sso/redirect
though.
Sorry for not being clear, I meant the ticket endpoint. The redirect endpoint must be /_matrix/client/r0/login/sso/redirect
as you noted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd let @kegsay or @neilalexander decide whether this should be done or not
Yeah I agree 😅
I meant the ticket endpoint
The ticket endpoint is /_matrix/client/r0/login/cas/ticket
according to the old spec, that could explain why synapse has it under /_matrix
as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2 endpoints please, both under /_matrix
will do. If we don't put it under /_matrix
then we need to mess around with routers more, and the only reason why we don't want it there is "just in case" the spec suddenly uses that endpoint for something, which feels unlikely.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a bunch of sytests here #1297 - some of them should be passing by now right? Please update sytest-whitelist
.
|
||
// check whether the token has a valid time. | ||
// TODO: should this 5 second window be configurable? | ||
if time.Now().Unix()-token.CreationTime > 5 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
5 seconds seems very short, perhaps 1 minute?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I only choose 5 seconds because the spec suggested it: https://matrix.org/docs/spec/client_server/r0.6.1#handling-the-authentication-endpoint
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh wow I see. In which case please just link to this on this line to justify why 5s.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
5 seconds sounds pretty short to me as well, if you have a connection or DNS failure that could easily time out. It seems that Synapse uses 2 minutes, although I was unable to find any reference to why that was chosen: https://github.com/matrix-org/synapse/blob/78d5f91de1a9baf4dbb0a794cb49a799f29f7a38/synapse/handlers/auth.py#L1323-L1325
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In that case, should I make this time configurable by the admin (with a default time of 5 seconds)?
|
||
// check whether the UserID is malformed | ||
if !strings.Contains(token.UserID, "@") { | ||
// TODO: should we reveal details about the error with the token or give vague responses instead? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Revealing details is fine for now.
// TODO: should we reveal details about the error with the token or give vague responses instead? | ||
return "", errors.New("Invalid UserID") | ||
} | ||
if _, err := userutil.ParseUsernameParam(token.UserID, serverName); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add some prose here to explain why this check alone is insufficient (it doesn't check there is an @
).
typePassword := auth.LoginTypePassword{ | ||
GetAccountByPassword: accountDB.GetAccountByPassword, | ||
Config: cfg, | ||
// TODO: is the the right way to read the body and re-add it? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes
// the service url that we send to CAS is homeserver.com/_matrix/client/r0/login/sso/redirect?redirectUrl=xyz | ||
ssoQueries.Set("service", req.RequestURI) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2 endpoints please, both under /_matrix
will do. If we don't put it under /_matrix
then we need to mess around with routers more, and the only reason why we don't want it there is "just in case" the spec suddenly uses that endpoint for something, which feels unlikely.
ssoURL string, | ||
) (string, error) { | ||
// make the call to the sso server to validate | ||
response, err := http.Get(ssoURL) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use an HTTP client here, as the default http.Get
has no timeout.
} | ||
|
||
// extract the response from the sso server | ||
data, err := ioutil.ReadAll(response.Body) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't forget to defer response.Body.Close()
} | ||
token, err := auth.GenerateLoginToken(account.UserID) | ||
if err != nil || token == "" { | ||
return util.JSONResponse{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please do not drop err
to the floor, log it.
This PR will be closed on 23/12/2020 without further input. |
Sorry. I got busy with other work, I will start working on this again this week (have time off at the end of the year) |
Closing this for now. Please feel free to re-open/ping when you get back to this. |
@anandv96 Can I take your code and fork this PR? |
Yes of course! There isn't too much left to do IIRC |
Thanks! Yeah, and the fact that you've already had review rounds is a strong motivator to not start over. :) |
This is forked from @anandv96's matrix-org#1374. Closes matrix-org#1297.
This is forked from @anandv96's matrix-org#1374. Closes matrix-org#1297.
This is forked from @anandv96's matrix-org#1374. Closes matrix-org#1297.
This is forked from @anandv96's matrix-org#1374. Closes matrix-org#1297.
Made some changes to implement CAS SSO support
m.login.sso
flow if the config is enabledlogin types include SSO
test passes if changes are made to the sytest dendrite config/login/sso/redirect
redirectUrl
with a login token to complete the loginClarifications needed
The spec mentions that a client should hit a User interactive flow endpoint to get a list of
flows
andstages
(https://matrix.org/docs/spec/client_server/latest#id186).To actually authenticate the user, the spec says that the client should use an
m.login.token
.Signed-off-by: Anand Vasudevan [email protected]
Pull Request Checklist
testfile
as specified in docs/sytest.md