Skip to content
This repository has been archived by the owner on Nov 25, 2024. It is now read-only.

[WIP] Implement m.login.token and SSO login initial changes #1374

Closed
wants to merge 2 commits into from

Conversation

anandv96
Copy link
Contributor

@anandv96 anandv96 commented Sep 1, 2020

Made some changes to implement CAS SSO support

  • made config changes for cas
  • GET /login returns the m.login.sso flow if the config is enabled
    • login types include SSO test passes if changes are made to the sytest dendrite config
  • redirect user to the CAS server upon hitting /login/sso/redirect
  • validate token from CAS server
  • create user if required
  • (pending) redirect client to the redirectUrl with a login token to complete the login
  • (pending) change sytest whitelist

Clarifications needed

  • The spec mentions that a client should hit a User interactive flow endpoint to get a list of flows and stages (https://matrix.org/docs/spec/client_server/latest#id186).

    • What endpoint needs to be hit? Not clear in the spec.
    • A few sytests are dependent on this
  • To actually authenticate the user, the spec says that the client should use an m.login.token.

    • Has this been implemented already? Couldn't find many references in the codebase
    • What kind of token is used here? How can I create this token?

Signed-off-by: Anand Vasudevan [email protected]

Pull Request Checklist

  • I have added any new tests that need to pass to testfile as specified in docs/sytest.md
  • Pull request includes a sign off

@kegsay
Copy link
Member

kegsay commented Sep 1, 2020

The spec mentions that a client should hit a User interactive flow endpoint to get a list of flows and stages - What endpoint needs to be hit?

In this case the /register endpoint. The term "user interactive auth" is used to describe the set of JSON responses returned from endpoints which implement it for authentication, so anything which requires special auth (registration, login, deleting devices, changing passwords) can use "user interactive auth" to provide a standardised way to authenticate these operations.

To actually authenticate the user, the spec says that the client should use an m.login.token.

We do not have token auth yet - see #403

@anandv96
Copy link
Contributor Author

anandv96 commented Sep 2, 2020

To actually authenticate the user, the spec says that the client should use an m.login.token.

We do not have token auth yet - see #403

#429 for m.login.token was closed without without merging, I'm not sure why though.

Should I work on #403 now before finishing #1297 ? Can #429 be modified and used?

@kegsay
Copy link
Member

kegsay commented Sep 2, 2020

Yes work on #403 first I think. The problem with #429 is that it used macaroons rather than just a random string like we do for many other things. You may be able to take bits of it and use it successfully though.

@anandv96
Copy link
Contributor Author

anandv96 commented Sep 2, 2020

The problem with #429 is that it used macaroons rather than just a random string like we do for many other things

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?

@neilalexander
Copy link
Contributor

Yes, I would use random strings for now.

Database level changes not made
@anandv96 anandv96 changed the title [WIP] Implement SSO login initial changes [WIP] Implement m.login.token and SSO login initial changes Sep 14, 2020
Copy link
Member

@kegsay kegsay left a 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.

if err != nil {
// TODO: is this appropriate?
return util.JSONResponse{
Code: http.StatusMethodNotAllowed,
Copy link
Member

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 Show resolved Hide resolved
if authErr != nil {
return *authErr

loginType := jsonBody["type"].(string)
Copy link
Member

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?

Copy link
Contributor Author

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

// 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?
Copy link
Member

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.

if !cfg.CAS.Enabled || cfg.CAS.Server == "" {
return util.JSONResponse{
Code: http.StatusMethodNotAllowed,
JSON: jsonerror.NotFound("Bad method"),
Copy link
Member

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.

Copy link
Contributor Author

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

}

// Try to parse the SSO URL configured to a url.URL type
ssoURL, err := url.Parse(cfg.CAS.Server)
Copy link
Member

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.

redirectURL, err := url.Parse(redirectURLStr)
if err != nil {
return util.JSONResponse{
Code: http.StatusInternalServerError,
Copy link
Member

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.

cfg *config.ClientAPI,
) util.JSONResponse {
// form the ticket validation URL from the config
ssoURL, err := url.Parse(cfg.CAS.Server + cfg.CAS.ValidateEndpoint)
Copy link
Member

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.

clientapi/routing/sso.go Show resolved Hide resolved
clientapi/routing/sso.go Outdated Show resolved Hide resolved
@anandv96
Copy link
Contributor Author

Made another commit with some of the changes from the comments.

I need some help with making database changes in clientapi/auth/login_token.go L179 onwards
https://github.com/matrix-org/dendrite/pull/1374/files#diff-1f4ba2edfc578ed53786cf30d3148eedR179

@anandv96
Copy link
Contributor Author

anandv96 commented Sep 21, 2020

I changed lib/SyTest/Homeserver/Dendrite.pm in sytest for the test login types include SSO to include the config values for SSO in the dendrite.yaml. Should I wait until all the other tests pass before I make the change in the sytest repo?

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
Comment on lines +82 to +83
// the service url that we send to CAS is homeserver.com/_matrix/client/r0/login/sso/redirect?redirectUrl=xyz
ssoQueries.Set("service", req.RequestURI)
Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Member

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.

Copy link
Member

@kegsay kegsay left a 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 {
Copy link
Member

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

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.

Copy link
Member

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

Copy link
Contributor Author

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?
Copy link
Member

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 {
Copy link
Member

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?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes

Comment on lines +82 to +83
// the service url that we send to CAS is homeserver.com/_matrix/client/r0/login/sso/redirect?redirectUrl=xyz
ssoQueries.Set("service", req.RequestURI)
Copy link
Member

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)
Copy link
Member

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)
Copy link
Member

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{
Copy link
Member

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.

@kegsay kegsay added the stale This issue or PR is at risk of being closed without further feedback label Dec 17, 2020
@kegsay
Copy link
Member

kegsay commented Dec 21, 2020

This PR will be closed on 23/12/2020 without further input.

@anandv96
Copy link
Contributor Author

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)

@kegsay
Copy link
Member

kegsay commented Jan 5, 2021

Closing this for now. Please feel free to re-open/ping when you get back to this.

@kegsay kegsay closed this Jan 5, 2021
@tommie
Copy link
Contributor

tommie commented Sep 25, 2021

@anandv96 Can I take your code and fork this PR?

@anandv96
Copy link
Contributor Author

@anandv96 Can I take your code and fork this PR?

Yes of course! There isn't too much left to do IIRC

@tommie
Copy link
Contributor

tommie commented Sep 26, 2021

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. :)

@tommie tommie mentioned this pull request Sep 26, 2021
2 tasks
tommie added a commit to tommie/dendrite that referenced this pull request Sep 27, 2021
tommie added a commit to tommie/dendrite that referenced this pull request May 23, 2022
tommie added a commit to tommie/dendrite that referenced this pull request May 23, 2022
samip5 pushed a commit to samip5/dendrite that referenced this pull request Jan 6, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
stale This issue or PR is at risk of being closed without further feedback
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants