ldap_test.go 11 KB

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