-
-
Notifications
You must be signed in to change notification settings - Fork 678
Conversation
pull request 20210407
#1813 will close |
can someone review the code or guide me what to do next? I'd love to perfect this pr can be merged. |
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.
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)"
cmd/sytest/sytest.go
Outdated
@@ -0,0 +1,114 @@ | |||
package main |
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 binary seems unrelated to this PR.
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 should be removed from here.
clientapi/auth/password.go
Outdated
if err != nil { | ||
return nil, &util.JSONResponse{ | ||
Code: http.StatusUnauthorized, | ||
JSON: jsonerror.InvalidUsername(err.Error()), |
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.
Unclear why this would be an invalid username. It's a badly configured LDAP address.
clientapi/auth/password.go
Outdated
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 |
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.
It might be easier to just have t.Config.LDAPAddr
which can then mux together TLS/host/port e.g ldaps://localhost:8008
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.
Good idea, it should be changed like this.
clientapi/auth/password.go
Outdated
if e1 != nil { | ||
return nil, &util.JSONResponse{ | ||
Code: http.StatusUnauthorized, | ||
JSON: jsonerror.InvalidUsername(err.Error()), |
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.
Should be using e1
not err
JSON: jsonerror.InvalidUsername(err.Error()), | ||
} | ||
} | ||
filter := fmt.Sprintf("(&%s(%s=%s))", t.Config.LDAP.Filter, "uid", localpart) |
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.
"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) |
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.
We probably want this to be customisable.
clientapi/auth/password.go
Outdated
if len(sr.Entries) == 0 { | ||
_, err = t.GetAccountByPassword(ctx, localpart, r.Password) | ||
if err != nil { | ||
return nil, &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.
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.
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.
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()), |
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 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?
clientapi/auth/password.go
Outdated
@@ -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 { |
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.
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, |
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 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.
Thank you very much for your guidance! |
I tried to merge the latest |
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. |
Pull Request Checklist
Login in to dendrite with username and password from ldap.
Signed-off-by:
jiafeng zheng <[email protected]>