-
-
Notifications
You must be signed in to change notification settings - Fork 675
Ldap #1877
Ldap #1877
Changes from all commits
19c8af7
1ba6606
ccbac43
fcfaed9
a2aeb7a
dc1b1bb
813205a
c2d4161
96d09c8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,8 +16,12 @@ package auth | |
|
||
import ( | ||
"context" | ||
"database/sql" | ||
"fmt" | ||
"net/http" | ||
|
||
"github.com/go-ldap/ldap/v3" | ||
|
||
"github.com/matrix-org/dendrite/clientapi/jsonerror" | ||
"github.com/matrix-org/dendrite/clientapi/userutil" | ||
"github.com/matrix-org/dendrite/setup/config" | ||
|
@@ -27,15 +31,19 @@ import ( | |
|
||
type GetAccountByPassword func(ctx context.Context, localpart, password string) (*api.Account, error) | ||
|
||
type GetAccountByLocalpart func(ctx context.Context, localpart string) (*api.Account, error) | ||
|
||
type PasswordRequest struct { | ||
Login | ||
Password string `json:"password"` | ||
} | ||
|
||
// LoginTypePassword implements https://matrix.org/docs/spec/client_server/r0.6.1#password-based | ||
type LoginTypePassword struct { | ||
GetAccountByPassword GetAccountByPassword | ||
Config *config.ClientAPI | ||
GetAccountByPassword GetAccountByPassword | ||
GetAccountByLocalpart GetAccountByLocalpart | ||
Config *config.ClientAPI | ||
UserAPI api.UserInternalAPI | ||
} | ||
|
||
func (t *LoginTypePassword) Name() string { | ||
|
@@ -46,6 +54,20 @@ func (t *LoginTypePassword) Request() interface{} { | |
return &PasswordRequest{} | ||
} | ||
|
||
func (t *LoginTypePassword) CheckPassword(ctx context.Context, localpart string, | ||
r *PasswordRequest) (*Login, *util.JSONResponse) { | ||
_, err := t.GetAccountByPassword(ctx, localpart, r.Password) | ||
if err != nil { | ||
// Technically we could tell them if the user does not exist by checking if err == sql.ErrNoRows | ||
// but that would leak the existence of the user. | ||
return nil, &util.JSONResponse{ | ||
Code: http.StatusForbidden, | ||
JSON: jsonerror.Forbidden("username or password was incorrect, or the account does not exist"), | ||
} | ||
} | ||
return &r.Login, nil | ||
} | ||
|
||
func (t *LoginTypePassword) Login(ctx context.Context, req interface{}) (*Login, *util.JSONResponse) { | ||
r := req.(*PasswordRequest) | ||
username := r.Username() | ||
|
@@ -62,14 +84,83 @@ func (t *LoginTypePassword) Login(ctx context.Context, req interface{}) (*Login, | |
JSON: jsonerror.InvalidUsername(err.Error()), | ||
} | ||
} | ||
_, err = t.GetAccountByPassword(ctx, localpart, r.Password) | ||
if err != nil { | ||
// Technically we could tell them if the user does not exist by checking if err == sql.ErrNoRows | ||
// but that would leak the existence of the user. | ||
return nil, &util.JSONResponse{ | ||
Code: http.StatusForbidden, | ||
JSON: jsonerror.Forbidden("username or password was incorrect, or the account does not exist"), | ||
if len(t.Config.LDAP.URI) > 0 { | ||
var conn *ldap.Conn | ||
conn, err = ldap.DialURL(t.Config.LDAP.URI) | ||
if err != nil { | ||
ise := jsonerror.InternalServerError() | ||
return nil, &ise | ||
} | ||
defer conn.Close() | ||
|
||
e1 := conn.Bind(t.Config.LDAP.BindDN, t.Config.LDAP.BindPSWD) | ||
if e1 != nil { | ||
ise := jsonerror.InternalServerError() | ||
return nil, &ise | ||
} | ||
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 commentThe reason will be displayed to describe this comment to others. Learn more. We probably want this to be customisable. |
||
var sr *ldap.SearchResult | ||
sr, err = conn.Search(searchRequest) | ||
if err != nil { | ||
return nil, &util.JSONResponse{ | ||
Code: http.StatusUnauthorized, | ||
JSON: jsonerror.InvalidUsername(err.Error()), | ||
} | ||
} | ||
if len(sr.Entries) > 1 { | ||
return nil, &util.JSONResponse{ | ||
Code: http.StatusUnauthorized, | ||
JSON: jsonerror.BadJSON("'user' must be duplicated."), | ||
} | ||
} | ||
if len(sr.Entries) == 0 { | ||
return t.CheckPassword(ctx, localpart, r) | ||
} | ||
|
||
userDN := sr.Entries[0].DN | ||
err = conn.Bind(userDN, r.Password) | ||
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 commentThe 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? |
||
} | ||
} | ||
|
||
_, err = t.GetAccountByLocalpart(ctx, localpart) | ||
if err != nil { | ||
if err == sql.ErrNoRows { | ||
var accRes api.PerformAccountCreationResponse | ||
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 commentThe 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. |
||
AccountType: api.AccountTypeUser, | ||
OnConflict: api.ConflictAbort, | ||
}, &accRes) | ||
if err != nil { | ||
if _, ok := err.(*api.ErrorConflict); ok { | ||
return nil, &util.JSONResponse{ | ||
Code: http.StatusBadRequest, | ||
JSON: jsonerror.UserInUse("Desired user ID is already taken."), | ||
} | ||
} | ||
return nil, &util.JSONResponse{ | ||
Code: http.StatusInternalServerError, | ||
JSON: jsonerror.Unknown("failed to create account: " + err.Error()), | ||
} | ||
} | ||
|
||
return &r.Login, nil | ||
} | ||
|
||
return nil, &util.JSONResponse{ | ||
Code: http.StatusUnauthorized, | ||
JSON: jsonerror.InvalidUsername(err.Error()), | ||
} | ||
} | ||
return &r.Login, nil | ||
} | ||
return &r.Login, nil | ||
|
||
return t.CheckPassword(ctx, localpart, r) | ||
} |
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 theSprintf
?