ldap_test.go 14 KB

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