mirror of
https://github.com/grafana/grafana.git
synced 2025-07-29 16:22:35 +08:00

* Users: add is_disabled column * Users: disable users removed from LDAP * Auth: return ErrInvalidCredentials for failed LDAP auth * User: return isDisabled flag in user search api * User: mark disabled users at the server admin page * Chore: refactor according to review * Auth: prevent disabled user from login * Auth: re-enable user when it found in ldap * User: add api endpoint for disabling user * User: use separate endpoints to disable/enable user * User: disallow disabling external users * User: able do disable users from admin UI * Chore: refactor based on review * Chore: use more clear error check when disabling user * Fix login tests * Tests for disabling user during the LDAP login * Tests for disable user API * Tests for login with disabled user * Remove disable user UI stub * Sync with latest LDAP refactoring
250 lines
6.9 KiB
Go
250 lines
6.9 KiB
Go
package ldap
|
|
|
|
import (
|
|
"testing"
|
|
|
|
. "github.com/smartystreets/goconvey/convey"
|
|
"gopkg.in/ldap.v3"
|
|
|
|
"github.com/grafana/grafana/pkg/infra/log"
|
|
"github.com/grafana/grafana/pkg/models"
|
|
"github.com/grafana/grafana/pkg/services/user"
|
|
)
|
|
|
|
func TestLDAPLogin(t *testing.T) {
|
|
Convey("Login()", t, func() {
|
|
authScenario("When user is log in and updated", func(sc *scenarioContext) {
|
|
// arrange
|
|
mockConnection := &mockConnection{}
|
|
|
|
auth := &Server{
|
|
config: &ServerConfig{
|
|
Host: "",
|
|
RootCACert: "",
|
|
Groups: []*GroupToOrgRole{
|
|
{GroupDN: "*", OrgRole: "Admin"},
|
|
},
|
|
Attr: AttributeMap{
|
|
Username: "username",
|
|
Surname: "surname",
|
|
Email: "email",
|
|
Name: "name",
|
|
MemberOf: "memberof",
|
|
},
|
|
SearchBaseDNs: []string{"BaseDNHere"},
|
|
},
|
|
connection: mockConnection,
|
|
log: log.New("test-logger"),
|
|
}
|
|
|
|
entry := ldap.Entry{
|
|
DN: "dn", Attributes: []*ldap.EntryAttribute{
|
|
{Name: "username", Values: []string{"roelgerrits"}},
|
|
{Name: "surname", Values: []string{"Gerrits"}},
|
|
{Name: "email", Values: []string{"roel@test.com"}},
|
|
{Name: "name", Values: []string{"Roel"}},
|
|
{Name: "memberof", Values: []string{"admins"}},
|
|
}}
|
|
result := ldap.SearchResult{Entries: []*ldap.Entry{&entry}}
|
|
mockConnection.setSearchResult(&result)
|
|
|
|
query := &models.LoginUserQuery{
|
|
Username: "roelgerrits",
|
|
}
|
|
|
|
sc.userQueryReturns(&models.User{
|
|
Id: 1,
|
|
Email: "roel@test.net",
|
|
Name: "Roel Gerrits",
|
|
Login: "roelgerrits",
|
|
})
|
|
sc.userOrgsQueryReturns([]*models.UserOrgDTO{})
|
|
|
|
// act
|
|
extUser, _ := auth.Login(query)
|
|
userInfo, err := user.Upsert(&user.UpsertArgs{
|
|
SignupAllowed: true,
|
|
ExternalUser: extUser,
|
|
})
|
|
|
|
// assert
|
|
|
|
// Check absence of the error
|
|
So(err, ShouldBeNil)
|
|
|
|
// User should be searched in ldap
|
|
So(mockConnection.searchCalled, ShouldBeTrue)
|
|
|
|
// Info should be updated (email differs)
|
|
So(userInfo.Email, ShouldEqual, "roel@test.com")
|
|
|
|
// User should have admin privileges
|
|
So(sc.addOrgUserCmd.Role, ShouldEqual, "Admin")
|
|
})
|
|
|
|
authScenario("When login with invalid credentials", func(scenario *scenarioContext) {
|
|
connection := &mockConnection{}
|
|
entry := ldap.Entry{}
|
|
result := ldap.SearchResult{Entries: []*ldap.Entry{&entry}}
|
|
connection.setSearchResult(&result)
|
|
|
|
connection.bindProvider = func(username, password string) error {
|
|
return &ldap.Error{
|
|
ResultCode: 49,
|
|
}
|
|
}
|
|
auth := &Server{
|
|
config: &ServerConfig{
|
|
Attr: AttributeMap{
|
|
Username: "username",
|
|
Name: "name",
|
|
MemberOf: "memberof",
|
|
},
|
|
SearchBaseDNs: []string{"BaseDNHere"},
|
|
},
|
|
connection: connection,
|
|
log: log.New("test-logger"),
|
|
}
|
|
|
|
_, err := auth.Login(scenario.loginUserQuery)
|
|
|
|
Convey("it should return invalid credentials error", func() {
|
|
So(err, ShouldEqual, ErrInvalidCredentials)
|
|
})
|
|
})
|
|
|
|
authScenario("When login with valid credentials", func(scenario *scenarioContext) {
|
|
connection := &mockConnection{}
|
|
entry := ldap.Entry{
|
|
DN: "dn", Attributes: []*ldap.EntryAttribute{
|
|
{Name: "username", Values: []string{"markelog"}},
|
|
{Name: "surname", Values: []string{"Gaidarenko"}},
|
|
{Name: "email", Values: []string{"markelog@gmail.com"}},
|
|
{Name: "name", Values: []string{"Oleg"}},
|
|
{Name: "memberof", Values: []string{"admins"}},
|
|
},
|
|
}
|
|
result := ldap.SearchResult{Entries: []*ldap.Entry{&entry}}
|
|
connection.setSearchResult(&result)
|
|
|
|
connection.bindProvider = func(username, password string) error {
|
|
return nil
|
|
}
|
|
auth := &Server{
|
|
config: &ServerConfig{
|
|
Attr: AttributeMap{
|
|
Username: "username",
|
|
Name: "name",
|
|
MemberOf: "memberof",
|
|
},
|
|
SearchBaseDNs: []string{"BaseDNHere"},
|
|
},
|
|
connection: connection,
|
|
log: log.New("test-logger"),
|
|
}
|
|
|
|
resp, err := auth.Login(scenario.loginUserQuery)
|
|
|
|
So(err, ShouldBeNil)
|
|
So(resp.Login, ShouldEqual, "markelog")
|
|
})
|
|
|
|
authScenario("When user not found in LDAP, but exist in Grafana", func(scenario *scenarioContext) {
|
|
connection := &mockConnection{}
|
|
result := ldap.SearchResult{Entries: []*ldap.Entry{}}
|
|
connection.setSearchResult(&result)
|
|
|
|
externalUser := &models.ExternalUserInfo{UserId: 42, IsDisabled: false}
|
|
scenario.getExternalUserInfoByLoginQueryReturns(externalUser)
|
|
|
|
connection.bindProvider = func(username, password string) error {
|
|
return nil
|
|
}
|
|
auth := &Server{
|
|
config: &ServerConfig{
|
|
SearchBaseDNs: []string{"BaseDNHere"},
|
|
},
|
|
connection: connection,
|
|
log: log.New("test-logger"),
|
|
}
|
|
|
|
_, err := auth.Login(scenario.loginUserQuery)
|
|
|
|
Convey("it should disable user", func() {
|
|
So(scenario.disableExternalUserCalled, ShouldBeTrue)
|
|
So(scenario.disableUserCmd.IsDisabled, ShouldBeTrue)
|
|
So(scenario.disableUserCmd.UserId, ShouldEqual, 42)
|
|
})
|
|
|
|
Convey("it should return invalid credentials error", func() {
|
|
So(err, ShouldEqual, ErrInvalidCredentials)
|
|
})
|
|
})
|
|
|
|
authScenario("When user not found in LDAP, and disabled in Grafana already", func(scenario *scenarioContext) {
|
|
connection := &mockConnection{}
|
|
result := ldap.SearchResult{Entries: []*ldap.Entry{}}
|
|
connection.setSearchResult(&result)
|
|
|
|
externalUser := &models.ExternalUserInfo{UserId: 42, IsDisabled: true}
|
|
scenario.getExternalUserInfoByLoginQueryReturns(externalUser)
|
|
|
|
connection.bindProvider = func(username, password string) error {
|
|
return nil
|
|
}
|
|
auth := &Server{
|
|
config: &ServerConfig{
|
|
SearchBaseDNs: []string{"BaseDNHere"},
|
|
},
|
|
connection: connection,
|
|
log: log.New("test-logger"),
|
|
}
|
|
|
|
_, err := auth.Login(scenario.loginUserQuery)
|
|
|
|
Convey("it should't call disable function", func() {
|
|
So(scenario.disableExternalUserCalled, ShouldBeFalse)
|
|
})
|
|
|
|
Convey("it should return invalid credentials error", func() {
|
|
So(err, ShouldEqual, ErrInvalidCredentials)
|
|
})
|
|
})
|
|
|
|
authScenario("When user found in LDAP, and disabled in Grafana", func(scenario *scenarioContext) {
|
|
connection := &mockConnection{}
|
|
entry := ldap.Entry{}
|
|
result := ldap.SearchResult{Entries: []*ldap.Entry{&entry}}
|
|
connection.setSearchResult(&result)
|
|
scenario.userQueryReturns(&models.User{Id: 42, IsDisabled: true})
|
|
|
|
connection.bindProvider = func(username, password string) error {
|
|
return nil
|
|
}
|
|
auth := &Server{
|
|
config: &ServerConfig{
|
|
SearchBaseDNs: []string{"BaseDNHere"},
|
|
},
|
|
connection: connection,
|
|
log: log.New("test-logger"),
|
|
}
|
|
|
|
extUser, _ := auth.Login(scenario.loginUserQuery)
|
|
_, err := user.Upsert(&user.UpsertArgs{
|
|
SignupAllowed: true,
|
|
ExternalUser: extUser,
|
|
})
|
|
|
|
Convey("it should re-enable user", func() {
|
|
So(scenario.disableExternalUserCalled, ShouldBeTrue)
|
|
So(scenario.disableUserCmd.IsDisabled, ShouldBeFalse)
|
|
So(scenario.disableUserCmd.UserId, ShouldEqual, 42)
|
|
})
|
|
|
|
Convey("it should not return error", func() {
|
|
So(err, ShouldBeNil)
|
|
})
|
|
})
|
|
})
|
|
}
|