ldap_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. package login
  2. import (
  3. "crypto/tls"
  4. "testing"
  5. "github.com/go-ldap/ldap"
  6. "github.com/grafana/grafana/pkg/bus"
  7. m "github.com/grafana/grafana/pkg/models"
  8. . "github.com/smartystreets/goconvey/convey"
  9. )
  10. func TestLdapAuther(t *testing.T) {
  11. Convey("When translating ldap user to grafana user", t, func() {
  12. Convey("Given no ldap group map match", func() {
  13. ldapAuther := NewLdapAuthenticator(&LdapServerConf{
  14. LdapGroups: []*LdapGroupToOrgRole{{}},
  15. })
  16. _, err := ldapAuther.GetGrafanaUserFor(nil, &LdapUserInfo{})
  17. So(err, ShouldEqual, ErrInvalidCredentials)
  18. })
  19. var user1 = &m.User{}
  20. ldapAutherScenario("Given wildcard group match", func(sc *scenarioContext) {
  21. ldapAuther := NewLdapAuthenticator(&LdapServerConf{
  22. LdapGroups: []*LdapGroupToOrgRole{
  23. {GroupDN: "*", OrgRole: "Admin"},
  24. },
  25. })
  26. sc.userQueryReturns(user1)
  27. result, err := ldapAuther.GetGrafanaUserFor(nil, &LdapUserInfo{})
  28. So(err, ShouldBeNil)
  29. So(result, ShouldEqual, user1)
  30. })
  31. ldapAutherScenario("Given exact group match", func(sc *scenarioContext) {
  32. ldapAuther := NewLdapAuthenticator(&LdapServerConf{
  33. LdapGroups: []*LdapGroupToOrgRole{
  34. {GroupDN: "cn=users", OrgRole: "Admin"},
  35. },
  36. })
  37. sc.userQueryReturns(user1)
  38. result, err := ldapAuther.GetGrafanaUserFor(nil, &LdapUserInfo{MemberOf: []string{"cn=users"}})
  39. So(err, ShouldBeNil)
  40. So(result, ShouldEqual, user1)
  41. })
  42. ldapAutherScenario("Given group match with different case", func(sc *scenarioContext) {
  43. ldapAuther := NewLdapAuthenticator(&LdapServerConf{
  44. LdapGroups: []*LdapGroupToOrgRole{
  45. {GroupDN: "cn=users", OrgRole: "Admin"},
  46. },
  47. })
  48. sc.userQueryReturns(user1)
  49. result, err := ldapAuther.GetGrafanaUserFor(&LdapUserInfo{MemberOf: []string{"CN=users"}})
  50. So(err, ShouldBeNil)
  51. So(result, ShouldEqual, user1)
  52. })
  53. ldapAutherScenario("Given no existing grafana user", func(sc *scenarioContext) {
  54. ldapAuther := NewLdapAuthenticator(&LdapServerConf{
  55. LdapGroups: []*LdapGroupToOrgRole{
  56. {GroupDN: "cn=admin", OrgRole: "Admin"},
  57. {GroupDN: "cn=editor", OrgRole: "Editor"},
  58. {GroupDN: "*", OrgRole: "Viewer"},
  59. },
  60. })
  61. sc.userQueryReturns(nil)
  62. result, err := ldapAuther.GetGrafanaUserFor(nil, &LdapUserInfo{
  63. DN: "torkelo",
  64. Username: "torkelo",
  65. Email: "my@email.com",
  66. MemberOf: []string{"cn=editor"},
  67. })
  68. So(err, ShouldBeNil)
  69. Convey("Should return new user", func() {
  70. So(result.Login, ShouldEqual, "torkelo")
  71. })
  72. })
  73. })
  74. Convey("When syncing ldap groups to grafana org roles", t, func() {
  75. ldapAutherScenario("given no current user orgs", func(sc *scenarioContext) {
  76. ldapAuther := NewLdapAuthenticator(&LdapServerConf{
  77. LdapGroups: []*LdapGroupToOrgRole{
  78. {GroupDN: "cn=users", OrgRole: "Admin"},
  79. },
  80. })
  81. sc.userOrgsQueryReturns([]*m.UserOrgDTO{})
  82. _, err := ldapAuther.GetGrafanaUserFor(nil, &LdapUserInfo{
  83. MemberOf: []string{"cn=users"},
  84. })
  85. Convey("Should create new org user", func() {
  86. So(err, ShouldBeNil)
  87. So(sc.addOrgUserCmd, ShouldNotBeNil)
  88. So(sc.addOrgUserCmd.Role, ShouldEqual, m.ROLE_ADMIN)
  89. })
  90. })
  91. ldapAutherScenario("given different current org role", func(sc *scenarioContext) {
  92. ldapAuther := NewLdapAuthenticator(&LdapServerConf{
  93. LdapGroups: []*LdapGroupToOrgRole{
  94. {GroupDN: "cn=users", OrgId: 1, OrgRole: "Admin"},
  95. },
  96. })
  97. sc.userOrgsQueryReturns([]*m.UserOrgDTO{{OrgId: 1, Role: m.ROLE_EDITOR}})
  98. _, err := ldapAuther.GetGrafanaUserFor(nil, &LdapUserInfo{
  99. MemberOf: []string{"cn=users"},
  100. })
  101. Convey("Should update org role", func() {
  102. So(err, ShouldBeNil)
  103. So(sc.updateOrgUserCmd, ShouldNotBeNil)
  104. So(sc.updateOrgUserCmd.Role, ShouldEqual, m.ROLE_ADMIN)
  105. So(sc.setUsingOrgCmd.OrgId, ShouldEqual, 1)
  106. })
  107. })
  108. ldapAutherScenario("given current org role is removed in ldap", func(sc *scenarioContext) {
  109. ldapAuther := NewLdapAuthenticator(&LdapServerConf{
  110. LdapGroups: []*LdapGroupToOrgRole{
  111. {GroupDN: "cn=users", OrgId: 2, OrgRole: "Admin"},
  112. },
  113. })
  114. sc.userOrgsQueryReturns([]*m.UserOrgDTO{
  115. {OrgId: 1, Role: m.ROLE_EDITOR},
  116. {OrgId: 2, Role: m.ROLE_EDITOR},
  117. })
  118. _, err := ldapAuther.GetGrafanaUserFor(nil, &LdapUserInfo{
  119. MemberOf: []string{"cn=users"},
  120. })
  121. Convey("Should remove org role", func() {
  122. So(err, ShouldBeNil)
  123. So(sc.removeOrgUserCmd, ShouldNotBeNil)
  124. So(sc.setUsingOrgCmd.OrgId, ShouldEqual, 2)
  125. })
  126. })
  127. ldapAutherScenario("given org role is updated in config", func(sc *scenarioContext) {
  128. ldapAuther := NewLdapAuthenticator(&LdapServerConf{
  129. LdapGroups: []*LdapGroupToOrgRole{
  130. {GroupDN: "cn=admin", OrgId: 1, OrgRole: "Admin"},
  131. {GroupDN: "cn=users", OrgId: 1, OrgRole: "Viewer"},
  132. },
  133. })
  134. sc.userOrgsQueryReturns([]*m.UserOrgDTO{{OrgId: 1, Role: m.ROLE_EDITOR}})
  135. _, err := ldapAuther.GetGrafanaUserFor(nil, &LdapUserInfo{
  136. MemberOf: []string{"cn=users"},
  137. })
  138. Convey("Should update org role", func() {
  139. So(err, ShouldBeNil)
  140. So(sc.removeOrgUserCmd, ShouldBeNil)
  141. So(sc.updateOrgUserCmd, ShouldNotBeNil)
  142. So(sc.setUsingOrgCmd.OrgId, ShouldEqual, 1)
  143. })
  144. })
  145. ldapAutherScenario("given multiple matching ldap groups", func(sc *scenarioContext) {
  146. ldapAuther := NewLdapAuthenticator(&LdapServerConf{
  147. LdapGroups: []*LdapGroupToOrgRole{
  148. {GroupDN: "cn=admins", OrgId: 1, OrgRole: "Admin"},
  149. {GroupDN: "*", OrgId: 1, OrgRole: "Viewer"},
  150. },
  151. })
  152. sc.userOrgsQueryReturns([]*m.UserOrgDTO{{OrgId: 1, Role: m.ROLE_ADMIN}})
  153. _, err := ldapAuther.GetGrafanaUserFor(nil, &LdapUserInfo{
  154. MemberOf: []string{"cn=admins"},
  155. })
  156. Convey("Should take first match, and ignore subsequent matches", func() {
  157. So(err, ShouldBeNil)
  158. So(sc.updateOrgUserCmd, ShouldBeNil)
  159. So(sc.setUsingOrgCmd.OrgId, ShouldEqual, 1)
  160. })
  161. })
  162. ldapAutherScenario("given multiple matching ldap groups and no existing groups", func(sc *scenarioContext) {
  163. ldapAuther := NewLdapAuthenticator(&LdapServerConf{
  164. LdapGroups: []*LdapGroupToOrgRole{
  165. {GroupDN: "cn=admins", OrgId: 1, OrgRole: "Admin"},
  166. {GroupDN: "*", OrgId: 1, OrgRole: "Viewer"},
  167. },
  168. })
  169. sc.userOrgsQueryReturns([]*m.UserOrgDTO{})
  170. _, err := ldapAuther.GetGrafanaUserFor(nil, &LdapUserInfo{
  171. MemberOf: []string{"cn=admins"},
  172. })
  173. Convey("Should take first match, and ignore subsequent matches", func() {
  174. So(err, ShouldBeNil)
  175. So(sc.addOrgUserCmd.Role, ShouldEqual, m.ROLE_ADMIN)
  176. So(sc.setUsingOrgCmd.OrgId, ShouldEqual, 1)
  177. })
  178. })
  179. })
  180. Convey("When calling SyncUser", t, func() {
  181. mockLdapConnection := &mockLdapConn{}
  182. ldapAuther := NewLdapAuthenticator(
  183. &LdapServerConf{
  184. Host: "",
  185. RootCACert: "",
  186. LdapGroups: []*LdapGroupToOrgRole{
  187. {GroupDN: "*", OrgRole: "Admin"},
  188. },
  189. Attr: LdapAttributeMap{
  190. Username: "username",
  191. Surname: "surname",
  192. Email: "email",
  193. Name: "name",
  194. MemberOf: "memberof",
  195. },
  196. SearchBaseDNs: []string{"BaseDNHere"},
  197. },
  198. )
  199. dialCalled := false
  200. ldapDial = func(network, addr string) (ILdapConn, error) {
  201. dialCalled = true
  202. return mockLdapConnection, nil
  203. }
  204. entry := ldap.Entry{
  205. DN: "dn", Attributes: []*ldap.EntryAttribute{
  206. {Name: "username", Values: []string{"roelgerrits"}},
  207. {Name: "surname", Values: []string{"Gerrits"}},
  208. {Name: "email", Values: []string{"roel@test.com"}},
  209. {Name: "name", Values: []string{"Roel"}},
  210. {Name: "memberof", Values: []string{"admins"}},
  211. }}
  212. result := ldap.SearchResult{Entries: []*ldap.Entry{&entry}}
  213. mockLdapConnection.setSearchResult(&result)
  214. ldapAutherScenario("When ldapUser found call syncInfo and orgRoles", func(sc *scenarioContext) {
  215. // arrange
  216. query := &m.LoginUserQuery{
  217. Username: "roelgerrits",
  218. }
  219. sc.userQueryReturns(&m.User{
  220. Id: 1,
  221. Email: "roel@test.net",
  222. Name: "Roel Gerrits",
  223. Login: "roelgerrits",
  224. })
  225. sc.userOrgsQueryReturns([]*m.UserOrgDTO{})
  226. // act
  227. syncErrResult := ldapAuther.SyncUser(query)
  228. // assert
  229. So(dialCalled, ShouldBeTrue)
  230. So(syncErrResult, ShouldBeNil)
  231. // User should be searched in ldap
  232. So(mockLdapConnection.searchCalled, ShouldBeTrue)
  233. // Info should be updated (email differs)
  234. So(sc.updateUserCmd.Email, ShouldEqual, "roel@test.com")
  235. // User should have admin privileges
  236. So(sc.addOrgUserCmd.UserId, ShouldEqual, 1)
  237. So(sc.addOrgUserCmd.Role, ShouldEqual, "Admin")
  238. })
  239. })
  240. }
  241. type mockLdapConn struct {
  242. result *ldap.SearchResult
  243. searchCalled bool
  244. }
  245. func (c *mockLdapConn) Bind(username, password string) error {
  246. return nil
  247. }
  248. func (c *mockLdapConn) Close() {}
  249. func (c *mockLdapConn) setSearchResult(result *ldap.SearchResult) {
  250. c.result = result
  251. }
  252. func (c *mockLdapConn) Search(*ldap.SearchRequest) (*ldap.SearchResult, error) {
  253. c.searchCalled = true
  254. return c.result, nil
  255. }
  256. func (c *mockLdapConn) StartTLS(*tls.Config) error {
  257. return nil
  258. }
  259. func ldapAutherScenario(desc string, fn scenarioFunc) {
  260. Convey(desc, func() {
  261. defer bus.ClearBusHandlers()
  262. sc := &scenarioContext{}
  263. bus.AddHandler("test", UpsertUser)
  264. bus.AddHandler("test", func(cmd *m.GetUserByAuthInfoQuery) error {
  265. sc.getUserByAuthInfoQuery = cmd
  266. sc.getUserByAuthInfoQuery.Result = &m.User{Login: cmd.Login}
  267. return nil
  268. })
  269. bus.AddHandler("test", func(cmd *m.GetUserOrgListQuery) error {
  270. sc.getUserOrgListQuery = cmd
  271. return nil
  272. })
  273. bus.AddHandler("test", func(cmd *m.CreateUserCommand) error {
  274. sc.createUserCmd = cmd
  275. sc.createUserCmd.Result = m.User{Login: cmd.Login}
  276. return nil
  277. })
  278. bus.AddHandler("test", func(cmd *m.AddOrgUserCommand) error {
  279. sc.addOrgUserCmd = cmd
  280. return nil
  281. })
  282. bus.AddHandler("test", func(cmd *m.UpdateOrgUserCommand) error {
  283. sc.updateOrgUserCmd = cmd
  284. return nil
  285. })
  286. bus.AddHandler("test", func(cmd *m.RemoveOrgUserCommand) error {
  287. sc.removeOrgUserCmd = cmd
  288. return nil
  289. })
  290. bus.AddHandler("test", func(cmd *m.UpdateUserCommand) error {
  291. sc.updateUserCmd = cmd
  292. return nil
  293. })
  294. bus.AddHandler("test", func(cmd *m.SetUsingOrgCommand) error {
  295. sc.setUsingOrgCmd = cmd
  296. return nil
  297. })
  298. fn(sc)
  299. })
  300. }
  301. type scenarioContext struct {
  302. getUserByAuthInfoQuery *m.GetUserByAuthInfoQuery
  303. getUserOrgListQuery *m.GetUserOrgListQuery
  304. createUserCmd *m.CreateUserCommand
  305. addOrgUserCmd *m.AddOrgUserCommand
  306. updateOrgUserCmd *m.UpdateOrgUserCommand
  307. removeOrgUserCmd *m.RemoveOrgUserCommand
  308. updateUserCmd *m.UpdateUserCommand
  309. setUsingOrgCmd *m.SetUsingOrgCommand
  310. }
  311. func (sc *scenarioContext) userQueryReturns(user *m.User) {
  312. bus.AddHandler("test", func(query *m.GetUserByAuthInfoQuery) error {
  313. if user == nil {
  314. return m.ErrUserNotFound
  315. }
  316. query.Result = user
  317. return nil
  318. })
  319. bus.AddHandler("test", func(query *m.SetAuthInfoCommand) error {
  320. return nil
  321. })
  322. }
  323. func (sc *scenarioContext) userOrgsQueryReturns(orgs []*m.UserOrgDTO) {
  324. bus.AddHandler("test", func(query *m.GetUserOrgListQuery) error {
  325. query.Result = orgs
  326. return nil
  327. })
  328. }
  329. type scenarioFunc func(c *scenarioContext)