ldap_test.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569
  1. package login
  2. import (
  3. "context"
  4. "crypto/tls"
  5. "testing"
  6. "github.com/grafana/grafana/pkg/bus"
  7. "github.com/grafana/grafana/pkg/log"
  8. m "github.com/grafana/grafana/pkg/models"
  9. . "github.com/smartystreets/goconvey/convey"
  10. "gopkg.in/ldap.v3"
  11. )
  12. func TestLdapAuther(t *testing.T) {
  13. Convey("initialBind", t, func() {
  14. Convey("Given bind dn and password configured", func() {
  15. conn := &mockLdapConn{}
  16. var actualUsername, actualPassword string
  17. conn.bindProvider = func(username, password string) error {
  18. actualUsername = username
  19. actualPassword = password
  20. return nil
  21. }
  22. ldapAuther := &ldapAuther{
  23. conn: conn,
  24. server: &LdapServerConf{
  25. BindDN: "cn=%s,o=users,dc=grafana,dc=org",
  26. BindPassword: "bindpwd",
  27. },
  28. }
  29. err := ldapAuther.initialBind("user", "pwd")
  30. So(err, ShouldBeNil)
  31. So(ldapAuther.requireSecondBind, ShouldBeTrue)
  32. So(actualUsername, ShouldEqual, "cn=user,o=users,dc=grafana,dc=org")
  33. So(actualPassword, ShouldEqual, "bindpwd")
  34. })
  35. Convey("Given bind dn configured", func() {
  36. conn := &mockLdapConn{}
  37. var actualUsername, actualPassword string
  38. conn.bindProvider = func(username, password string) error {
  39. actualUsername = username
  40. actualPassword = password
  41. return nil
  42. }
  43. ldapAuther := &ldapAuther{
  44. conn: conn,
  45. server: &LdapServerConf{
  46. BindDN: "cn=%s,o=users,dc=grafana,dc=org",
  47. },
  48. }
  49. err := ldapAuther.initialBind("user", "pwd")
  50. So(err, ShouldBeNil)
  51. So(ldapAuther.requireSecondBind, ShouldBeFalse)
  52. So(actualUsername, ShouldEqual, "cn=user,o=users,dc=grafana,dc=org")
  53. So(actualPassword, ShouldEqual, "pwd")
  54. })
  55. Convey("Given empty bind dn and password", func() {
  56. conn := &mockLdapConn{}
  57. unauthenticatedBindWasCalled := false
  58. var actualUsername string
  59. conn.unauthenticatedBindProvider = func(username string) error {
  60. unauthenticatedBindWasCalled = true
  61. actualUsername = username
  62. return nil
  63. }
  64. ldapAuther := &ldapAuther{
  65. conn: conn,
  66. server: &LdapServerConf{},
  67. }
  68. err := ldapAuther.initialBind("user", "pwd")
  69. So(err, ShouldBeNil)
  70. So(ldapAuther.requireSecondBind, ShouldBeTrue)
  71. So(unauthenticatedBindWasCalled, ShouldBeTrue)
  72. So(actualUsername, ShouldBeEmpty)
  73. })
  74. })
  75. Convey("When translating ldap user to grafana user", t, func() {
  76. var user1 = &m.User{}
  77. bus.AddHandlerCtx("test", func(ctx context.Context, cmd *m.UpsertUserCommand) error {
  78. cmd.Result = user1
  79. cmd.Result.Login = "torkelo"
  80. return nil
  81. })
  82. Convey("Given no ldap group map match", func() {
  83. ldapAuther := NewLdapAuthenticator(&LdapServerConf{
  84. LdapGroups: []*LdapGroupToOrgRole{{}},
  85. })
  86. _, err := ldapAuther.GetGrafanaUserFor(nil, &LdapUserInfo{})
  87. So(err, ShouldEqual, ErrInvalidCredentials)
  88. })
  89. ldapAutherScenario("Given wildcard group match", func(sc *scenarioContext) {
  90. ldapAuther := NewLdapAuthenticator(&LdapServerConf{
  91. LdapGroups: []*LdapGroupToOrgRole{
  92. {GroupDN: "*", OrgRole: "Admin"},
  93. },
  94. })
  95. sc.userQueryReturns(user1)
  96. result, err := ldapAuther.GetGrafanaUserFor(nil, &LdapUserInfo{})
  97. So(err, ShouldBeNil)
  98. So(result, ShouldEqual, user1)
  99. })
  100. ldapAutherScenario("Given exact group match", func(sc *scenarioContext) {
  101. ldapAuther := NewLdapAuthenticator(&LdapServerConf{
  102. LdapGroups: []*LdapGroupToOrgRole{
  103. {GroupDN: "cn=users", OrgRole: "Admin"},
  104. },
  105. })
  106. sc.userQueryReturns(user1)
  107. result, err := ldapAuther.GetGrafanaUserFor(nil, &LdapUserInfo{MemberOf: []string{"cn=users"}})
  108. So(err, ShouldBeNil)
  109. So(result, ShouldEqual, user1)
  110. })
  111. ldapAutherScenario("Given group match with different case", func(sc *scenarioContext) {
  112. ldapAuther := NewLdapAuthenticator(&LdapServerConf{
  113. LdapGroups: []*LdapGroupToOrgRole{
  114. {GroupDN: "cn=users", OrgRole: "Admin"},
  115. },
  116. })
  117. sc.userQueryReturns(user1)
  118. result, err := ldapAuther.GetGrafanaUserFor(nil, &LdapUserInfo{MemberOf: []string{"CN=users"}})
  119. So(err, ShouldBeNil)
  120. So(result, ShouldEqual, user1)
  121. })
  122. ldapAutherScenario("Given no existing grafana user", func(sc *scenarioContext) {
  123. ldapAuther := NewLdapAuthenticator(&LdapServerConf{
  124. LdapGroups: []*LdapGroupToOrgRole{
  125. {GroupDN: "cn=admin", OrgRole: "Admin"},
  126. {GroupDN: "cn=editor", OrgRole: "Editor"},
  127. {GroupDN: "*", OrgRole: "Viewer"},
  128. },
  129. })
  130. sc.userQueryReturns(nil)
  131. result, err := ldapAuther.GetGrafanaUserFor(nil, &LdapUserInfo{
  132. DN: "torkelo",
  133. Username: "torkelo",
  134. Email: "my@email.com",
  135. MemberOf: []string{"cn=editor"},
  136. })
  137. So(err, ShouldBeNil)
  138. Convey("Should return new user", func() {
  139. So(result.Login, ShouldEqual, "torkelo")
  140. })
  141. Convey("Should set isGrafanaAdmin to false by default", func() {
  142. So(result.IsAdmin, ShouldBeFalse)
  143. })
  144. })
  145. })
  146. Convey("When syncing ldap groups to grafana org roles", t, func() {
  147. ldapAutherScenario("given no current user orgs", func(sc *scenarioContext) {
  148. ldapAuther := NewLdapAuthenticator(&LdapServerConf{
  149. LdapGroups: []*LdapGroupToOrgRole{
  150. {GroupDN: "cn=users", OrgRole: "Admin"},
  151. },
  152. })
  153. sc.userOrgsQueryReturns([]*m.UserOrgDTO{})
  154. _, err := ldapAuther.GetGrafanaUserFor(nil, &LdapUserInfo{
  155. MemberOf: []string{"cn=users"},
  156. })
  157. Convey("Should create new org user", func() {
  158. So(err, ShouldBeNil)
  159. So(sc.addOrgUserCmd, ShouldNotBeNil)
  160. So(sc.addOrgUserCmd.Role, ShouldEqual, m.ROLE_ADMIN)
  161. })
  162. })
  163. ldapAutherScenario("given different current org role", func(sc *scenarioContext) {
  164. ldapAuther := NewLdapAuthenticator(&LdapServerConf{
  165. LdapGroups: []*LdapGroupToOrgRole{
  166. {GroupDN: "cn=users", OrgId: 1, OrgRole: "Admin"},
  167. },
  168. })
  169. sc.userOrgsQueryReturns([]*m.UserOrgDTO{{OrgId: 1, Role: m.ROLE_EDITOR}})
  170. _, err := ldapAuther.GetGrafanaUserFor(nil, &LdapUserInfo{
  171. MemberOf: []string{"cn=users"},
  172. })
  173. Convey("Should update org role", func() {
  174. So(err, ShouldBeNil)
  175. So(sc.updateOrgUserCmd, ShouldNotBeNil)
  176. So(sc.updateOrgUserCmd.Role, ShouldEqual, m.ROLE_ADMIN)
  177. So(sc.setUsingOrgCmd.OrgId, ShouldEqual, 1)
  178. })
  179. })
  180. ldapAutherScenario("given current org role is removed in ldap", func(sc *scenarioContext) {
  181. ldapAuther := NewLdapAuthenticator(&LdapServerConf{
  182. LdapGroups: []*LdapGroupToOrgRole{
  183. {GroupDN: "cn=users", OrgId: 2, OrgRole: "Admin"},
  184. },
  185. })
  186. sc.userOrgsQueryReturns([]*m.UserOrgDTO{
  187. {OrgId: 1, Role: m.ROLE_EDITOR},
  188. {OrgId: 2, Role: m.ROLE_EDITOR},
  189. })
  190. _, err := ldapAuther.GetGrafanaUserFor(nil, &LdapUserInfo{
  191. MemberOf: []string{"cn=users"},
  192. })
  193. Convey("Should remove org role", func() {
  194. So(err, ShouldBeNil)
  195. So(sc.removeOrgUserCmd, ShouldNotBeNil)
  196. So(sc.setUsingOrgCmd.OrgId, ShouldEqual, 2)
  197. })
  198. })
  199. ldapAutherScenario("given org role is updated in config", func(sc *scenarioContext) {
  200. ldapAuther := NewLdapAuthenticator(&LdapServerConf{
  201. LdapGroups: []*LdapGroupToOrgRole{
  202. {GroupDN: "cn=admin", OrgId: 1, OrgRole: "Admin"},
  203. {GroupDN: "cn=users", OrgId: 1, OrgRole: "Viewer"},
  204. },
  205. })
  206. sc.userOrgsQueryReturns([]*m.UserOrgDTO{{OrgId: 1, Role: m.ROLE_EDITOR}})
  207. _, err := ldapAuther.GetGrafanaUserFor(nil, &LdapUserInfo{
  208. MemberOf: []string{"cn=users"},
  209. })
  210. Convey("Should update org role", func() {
  211. So(err, ShouldBeNil)
  212. So(sc.removeOrgUserCmd, ShouldBeNil)
  213. So(sc.updateOrgUserCmd, ShouldNotBeNil)
  214. So(sc.setUsingOrgCmd.OrgId, ShouldEqual, 1)
  215. })
  216. })
  217. ldapAutherScenario("given multiple matching ldap groups", func(sc *scenarioContext) {
  218. ldapAuther := NewLdapAuthenticator(&LdapServerConf{
  219. LdapGroups: []*LdapGroupToOrgRole{
  220. {GroupDN: "cn=admins", OrgId: 1, OrgRole: "Admin"},
  221. {GroupDN: "*", OrgId: 1, OrgRole: "Viewer"},
  222. },
  223. })
  224. sc.userOrgsQueryReturns([]*m.UserOrgDTO{{OrgId: 1, Role: m.ROLE_ADMIN}})
  225. _, err := ldapAuther.GetGrafanaUserFor(nil, &LdapUserInfo{
  226. MemberOf: []string{"cn=admins"},
  227. })
  228. Convey("Should take first match, and ignore subsequent matches", func() {
  229. So(err, ShouldBeNil)
  230. So(sc.updateOrgUserCmd, ShouldBeNil)
  231. So(sc.setUsingOrgCmd.OrgId, ShouldEqual, 1)
  232. })
  233. })
  234. ldapAutherScenario("given multiple matching ldap groups and no existing groups", func(sc *scenarioContext) {
  235. ldapAuther := NewLdapAuthenticator(&LdapServerConf{
  236. LdapGroups: []*LdapGroupToOrgRole{
  237. {GroupDN: "cn=admins", OrgId: 1, OrgRole: "Admin"},
  238. {GroupDN: "*", OrgId: 1, OrgRole: "Viewer"},
  239. },
  240. })
  241. sc.userOrgsQueryReturns([]*m.UserOrgDTO{})
  242. _, err := ldapAuther.GetGrafanaUserFor(nil, &LdapUserInfo{
  243. MemberOf: []string{"cn=admins"},
  244. })
  245. Convey("Should take first match, and ignore subsequent matches", func() {
  246. So(err, ShouldBeNil)
  247. So(sc.addOrgUserCmd.Role, ShouldEqual, m.ROLE_ADMIN)
  248. So(sc.setUsingOrgCmd.OrgId, ShouldEqual, 1)
  249. })
  250. Convey("Should not update permissions unless specified", func() {
  251. So(err, ShouldBeNil)
  252. So(sc.updateUserPermissionsCmd, ShouldBeNil)
  253. })
  254. })
  255. ldapAutherScenario("given ldap groups with grafana_admin=true", func(sc *scenarioContext) {
  256. trueVal := true
  257. ldapAuther := NewLdapAuthenticator(&LdapServerConf{
  258. LdapGroups: []*LdapGroupToOrgRole{
  259. {GroupDN: "cn=admins", OrgId: 1, OrgRole: "Admin", IsGrafanaAdmin: &trueVal},
  260. },
  261. })
  262. sc.userOrgsQueryReturns([]*m.UserOrgDTO{})
  263. _, err := ldapAuther.GetGrafanaUserFor(nil, &LdapUserInfo{
  264. MemberOf: []string{"cn=admins"},
  265. })
  266. Convey("Should create user with admin set to true", func() {
  267. So(err, ShouldBeNil)
  268. So(sc.updateUserPermissionsCmd.IsGrafanaAdmin, ShouldBeTrue)
  269. })
  270. })
  271. })
  272. Convey("When calling SyncUser", t, func() {
  273. mockLdapConnection := &mockLdapConn{}
  274. ldapAuther := NewLdapAuthenticator(
  275. &LdapServerConf{
  276. Host: "",
  277. RootCACert: "",
  278. LdapGroups: []*LdapGroupToOrgRole{
  279. {GroupDN: "*", OrgRole: "Admin"},
  280. },
  281. Attr: LdapAttributeMap{
  282. Username: "username",
  283. Surname: "surname",
  284. Email: "email",
  285. Name: "name",
  286. MemberOf: "memberof",
  287. },
  288. SearchBaseDNs: []string{"BaseDNHere"},
  289. },
  290. )
  291. dialCalled := false
  292. ldapDial = func(network, addr string) (ILdapConn, error) {
  293. dialCalled = true
  294. return mockLdapConnection, nil
  295. }
  296. entry := ldap.Entry{
  297. DN: "dn", Attributes: []*ldap.EntryAttribute{
  298. {Name: "username", Values: []string{"roelgerrits"}},
  299. {Name: "surname", Values: []string{"Gerrits"}},
  300. {Name: "email", Values: []string{"roel@test.com"}},
  301. {Name: "name", Values: []string{"Roel"}},
  302. {Name: "memberof", Values: []string{"admins"}},
  303. }}
  304. result := ldap.SearchResult{Entries: []*ldap.Entry{&entry}}
  305. mockLdapConnection.setSearchResult(&result)
  306. ldapAutherScenario("When ldapUser found call syncInfo and orgRoles", func(sc *scenarioContext) {
  307. // arrange
  308. query := &m.LoginUserQuery{
  309. Username: "roelgerrits",
  310. }
  311. sc.userQueryReturns(&m.User{
  312. Id: 1,
  313. Email: "roel@test.net",
  314. Name: "Roel Gerrits",
  315. Login: "roelgerrits",
  316. })
  317. sc.userOrgsQueryReturns([]*m.UserOrgDTO{})
  318. // act
  319. syncErrResult := ldapAuther.SyncUser(query)
  320. // assert
  321. So(dialCalled, ShouldBeTrue)
  322. So(syncErrResult, ShouldBeNil)
  323. // User should be searched in ldap
  324. So(mockLdapConnection.searchCalled, ShouldBeTrue)
  325. // Info should be updated (email differs)
  326. So(sc.updateUserCmd.Email, ShouldEqual, "roel@test.com")
  327. // User should have admin privileges
  328. So(sc.addOrgUserCmd.UserId, ShouldEqual, 1)
  329. So(sc.addOrgUserCmd.Role, ShouldEqual, "Admin")
  330. })
  331. })
  332. Convey("When searching for a user and not all five attributes are mapped", t, func() {
  333. mockLdapConnection := &mockLdapConn{}
  334. entry := ldap.Entry{
  335. DN: "dn", Attributes: []*ldap.EntryAttribute{
  336. {Name: "username", Values: []string{"roelgerrits"}},
  337. {Name: "surname", Values: []string{"Gerrits"}},
  338. {Name: "email", Values: []string{"roel@test.com"}},
  339. {Name: "name", Values: []string{"Roel"}},
  340. {Name: "memberof", Values: []string{"admins"}},
  341. }}
  342. result := ldap.SearchResult{Entries: []*ldap.Entry{&entry}}
  343. mockLdapConnection.setSearchResult(&result)
  344. // Set up attribute map without surname and email
  345. ldapAuther := &ldapAuther{
  346. server: &LdapServerConf{
  347. Attr: LdapAttributeMap{
  348. Username: "username",
  349. Name: "name",
  350. MemberOf: "memberof",
  351. },
  352. SearchBaseDNs: []string{"BaseDNHere"},
  353. },
  354. conn: mockLdapConnection,
  355. log: log.New("test-logger"),
  356. }
  357. searchResult, err := ldapAuther.searchForUser("roelgerrits")
  358. So(err, ShouldBeNil)
  359. So(searchResult, ShouldNotBeNil)
  360. // User should be searched in ldap
  361. So(mockLdapConnection.searchCalled, ShouldBeTrue)
  362. // No empty attributes should be added to the search request
  363. So(len(mockLdapConnection.searchAttributes), ShouldEqual, 3)
  364. })
  365. }
  366. type mockLdapConn struct {
  367. result *ldap.SearchResult
  368. searchCalled bool
  369. searchAttributes []string
  370. bindProvider func(username, password string) error
  371. unauthenticatedBindProvider func(username string) error
  372. }
  373. func (c *mockLdapConn) Bind(username, password string) error {
  374. if c.bindProvider != nil {
  375. return c.bindProvider(username, password)
  376. }
  377. return nil
  378. }
  379. func (c *mockLdapConn) UnauthenticatedBind(username string) error {
  380. if c.unauthenticatedBindProvider != nil {
  381. return c.unauthenticatedBindProvider(username)
  382. }
  383. return nil
  384. }
  385. func (c *mockLdapConn) Close() {}
  386. func (c *mockLdapConn) setSearchResult(result *ldap.SearchResult) {
  387. c.result = result
  388. }
  389. func (c *mockLdapConn) Search(sr *ldap.SearchRequest) (*ldap.SearchResult, error) {
  390. c.searchCalled = true
  391. c.searchAttributes = sr.Attributes
  392. return c.result, nil
  393. }
  394. func (c *mockLdapConn) StartTLS(*tls.Config) error {
  395. return nil
  396. }
  397. func ldapAutherScenario(desc string, fn scenarioFunc) {
  398. Convey(desc, func() {
  399. defer bus.ClearBusHandlers()
  400. sc := &scenarioContext{}
  401. loginService := &LoginService{
  402. Bus: bus.GetBus(),
  403. }
  404. bus.AddHandler("test", loginService.UpsertUser)
  405. bus.AddHandlerCtx("test", func(ctx context.Context, cmd *m.SyncTeamsCommand) error {
  406. return nil
  407. })
  408. bus.AddHandlerCtx("test", func(ctx context.Context, cmd *m.UpdateUserPermissionsCommand) error {
  409. sc.updateUserPermissionsCmd = cmd
  410. return nil
  411. })
  412. bus.AddHandler("test", func(cmd *m.GetUserByAuthInfoQuery) error {
  413. sc.getUserByAuthInfoQuery = cmd
  414. sc.getUserByAuthInfoQuery.Result = &m.User{Login: cmd.Login}
  415. return nil
  416. })
  417. bus.AddHandler("test", func(cmd *m.GetUserOrgListQuery) error {
  418. sc.getUserOrgListQuery = cmd
  419. return nil
  420. })
  421. bus.AddHandler("test", func(cmd *m.CreateUserCommand) error {
  422. sc.createUserCmd = cmd
  423. sc.createUserCmd.Result = m.User{Login: cmd.Login}
  424. return nil
  425. })
  426. bus.AddHandler("test", func(cmd *m.AddOrgUserCommand) error {
  427. sc.addOrgUserCmd = cmd
  428. return nil
  429. })
  430. bus.AddHandler("test", func(cmd *m.UpdateOrgUserCommand) error {
  431. sc.updateOrgUserCmd = cmd
  432. return nil
  433. })
  434. bus.AddHandler("test", func(cmd *m.RemoveOrgUserCommand) error {
  435. sc.removeOrgUserCmd = cmd
  436. return nil
  437. })
  438. bus.AddHandler("test", func(cmd *m.UpdateUserCommand) error {
  439. sc.updateUserCmd = cmd
  440. return nil
  441. })
  442. bus.AddHandler("test", func(cmd *m.SetUsingOrgCommand) error {
  443. sc.setUsingOrgCmd = cmd
  444. return nil
  445. })
  446. fn(sc)
  447. })
  448. }
  449. type scenarioContext struct {
  450. getUserByAuthInfoQuery *m.GetUserByAuthInfoQuery
  451. getUserOrgListQuery *m.GetUserOrgListQuery
  452. createUserCmd *m.CreateUserCommand
  453. addOrgUserCmd *m.AddOrgUserCommand
  454. updateOrgUserCmd *m.UpdateOrgUserCommand
  455. removeOrgUserCmd *m.RemoveOrgUserCommand
  456. updateUserCmd *m.UpdateUserCommand
  457. setUsingOrgCmd *m.SetUsingOrgCommand
  458. updateUserPermissionsCmd *m.UpdateUserPermissionsCommand
  459. }
  460. func (sc *scenarioContext) userQueryReturns(user *m.User) {
  461. bus.AddHandler("test", func(query *m.GetUserByAuthInfoQuery) error {
  462. if user == nil {
  463. return m.ErrUserNotFound
  464. }
  465. query.Result = user
  466. return nil
  467. })
  468. bus.AddHandler("test", func(query *m.SetAuthInfoCommand) error {
  469. return nil
  470. })
  471. }
  472. func (sc *scenarioContext) userOrgsQueryReturns(orgs []*m.UserOrgDTO) {
  473. bus.AddHandler("test", func(query *m.GetUserOrgListQuery) error {
  474. query.Result = orgs
  475. return nil
  476. })
  477. }
  478. type scenarioFunc func(c *scenarioContext)