ldap_test.go 14 KB

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