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

Ldap #1877

Closed
wants to merge 9 commits into from
Closed

Ldap #1877

wants to merge 9 commits into from

Conversation

phoenix-2021
Copy link

Pull Request Checklist

Login in to dendrite with username and password from ldap.

Signed-off-by: jiafeng zheng <[email protected]>

@DoM1niC
Copy link

DoM1niC commented Jun 18, 2021

#1813 will close

@phoenix-2021
Copy link
Author

can someone review the code or guide me what to do next? I'd love to perfect this pr can be merged.

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.

Aside from comments, this is a working opt-in LDAP solution. Dendrite will need an LDAP solution inevitably in the future. However, I don't know if we're at that point yet. There's numerous things to consider which this PR doesn't do yet:

  • Where in the LDAP directory to search: this PR searches everywhere the bind user has permission to.
  • Support for anonymous clients (that is, no bind user at all)
  • Any kind of SASL authentication (specifically ones which are more secure and do not send the plaintext password to the server, e.g RFC2222

We would also need some sort of Complement tests for this realistically. Thoughts @neilalexander ?

For comparison, Synapse has LDAP support, with a config that looks like:

password_providers:
#    # Example config for an LDAP auth provider
#    - module: "ldap_auth_provider.LdapAuthProvider"
#      config:
#        enabled: true
#        uri: "ldap://ldap.example.com:389"
#        start_tls: true
#        base: "ou=users,dc=example,dc=com"
#        attributes:
#           uid: "cn"
#           mail: "email"
#           name: "givenName"
#        #bind_dn:
#        #bind_password:
#        #filter: "(objectClass=posixAccount)"

@@ -0,0 +1,114 @@
package main
Copy link
Member

Choose a reason for hiding this comment

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

This binary seems unrelated to this PR.

Copy link
Author

Choose a reason for hiding this comment

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

Yes, it should be removed from here.

if err != nil {
return nil, &util.JSONResponse{
Code: http.StatusUnauthorized,
JSON: jsonerror.InvalidUsername(err.Error()),
Copy link
Member

Choose a reason for hiding this comment

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

Unclear why this would be an invalid username. It's a badly configured LDAP address.

if t.Config.LDAP.TLS {
addr = "ldaps://" + t.Config.LDAP.Host + ":" + t.Config.LDAP.Port
} else {
addr = "ldap://" + t.Config.LDAP.Host + ":" + t.Config.LDAP.Port
Copy link
Member

Choose a reason for hiding this comment

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

It might be easier to just have t.Config.LDAPAddr which can then mux together TLS/host/port e.g ldaps://localhost:8008

Copy link
Author

Choose a reason for hiding this comment

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

Good idea, it should be changed like this.

if e1 != nil {
return nil, &util.JSONResponse{
Code: http.StatusUnauthorized,
JSON: jsonerror.InvalidUsername(err.Error()),
Copy link
Member

Choose a reason for hiding this comment

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

Should be using e1 not err

JSON: jsonerror.InvalidUsername(err.Error()),
}
}
filter := fmt.Sprintf("(&%s(%s=%s))", t.Config.LDAP.Filter, "uid", localpart)
Copy link
Member

Choose a reason for hiding this comment

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

"uid" can just be put into the Sprintf?

}
}
filter := fmt.Sprintf("(&%s(%s=%s))", t.Config.LDAP.Filter, "uid", localpart)
searchRequest := ldap.NewSearchRequest(t.Config.LDAP.BaseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, filter, []string{"uid"}, nil)
Copy link
Member

Choose a reason for hiding this comment

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

We probably want this to be customisable.

if len(sr.Entries) == 0 {
_, err = t.GetAccountByPassword(ctx, localpart, r.Password)
if err != nil {
return nil, &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.

Rather than duplicating the code on :168-176 it would be better to fork the LDAP code into a separate function and communicate success/failure/fatal failure that way.

Copy link
Contributor

@neilalexander neilalexander left a comment

Choose a reason for hiding this comment

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

Thanks for the contribution! Ultimately this PR seems reasonable but I agree with @kegsay that we need to support, at a minimum:

  • restricting the search base to specific parts of the directory
  • restricting authentication to certain LDAP group memberships
  • avoiding plain-text auth

It's also important that we get the account management flow right here — for example, some questions to consider:

  • what happens if you delete or disable an LDAP account after creation?
  • can we convert an LDAP account to a local user or back?
  • what happens if you have local users and then enable LDAP later?

if err != nil {
return nil, &util.JSONResponse{
Code: http.StatusUnauthorized,
JSON: jsonerror.InvalidUsername(err.Error()),
Copy link
Contributor

Choose a reason for hiding this comment

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

I suspect this is the wrong return code for an invalid bind DN, since this error code will be returned to the client as if the user had entered a wrong account password?

@@ -62,6 +70,101 @@ func (t *LoginTypePassword) Login(ctx context.Context, req interface{}) (*Login,
JSON: jsonerror.InvalidUsername(err.Error()),
}
}
if len(t.Config.LDAP.Host) > 0 {
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this mean that we can't have local accounts in addition to LDAP accounts?

err = t.UserAPI.PerformAccountCreation(ctx, &api.PerformAccountCreationRequest{
AppServiceID: "",
Localpart: localpart,
Password: r.Password,
Copy link
Contributor

Choose a reason for hiding this comment

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

This looks like we replicate the user's LDAP password from the point of their first login into the Dendrite account database, which I don't think we want to do since we should probably already be asking LDAP each time the user authenticates with their current password.

@phoenix-2021
Copy link
Author

phoenix-2021 commented Aug 2, 2021

Thanks for the contribution! Ultimately this PR seems reasonable but I agree with @kegsay that we need to support, at a minimum:

  • restricting the search base to specific parts of the directory
  • restricting authentication to certain LDAP group memberships
  • avoiding plain-text auth

It's also important that we get the account management flow right here — for example, some questions to consider:

  • what happens if you delete or disable an LDAP account after creation?
  • can we convert an LDAP account to a local user or back?
  • what happens if you have local users and then enable LDAP later?

Thank you very much for your guidance!
Since I am not familiar with dendrite and matrix, I have no idea how to deal with the relationship between local accounts and ldap, but I will continue to understand.
I have a small question, according to my understanding of LDAP, field LDAP.BaseDN in config can be used to specify the search scope, field LDAP.Filter can be used to filter groups, configured by the administrator here.

@phoenix-2021 phoenix-2021 requested a review from a team as a code owner February 4, 2022 17:48
@kegsay kegsay self-assigned this Feb 4, 2022
@neilalexander
Copy link
Contributor

I tried to merge the latest main commits into this branch but it appears the PR doesn't allow maintainer edits. Please check the Allow edits from maintainers box.

@neilalexander
Copy link
Contributor

Closing this for now because it is stale. If you get some bandwidth for this then please let us know and we can reopen it.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants