ldap_test.go 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  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(&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(&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(&LdapUserInfo{MemberOf: []string{"cn=users"}})
  39. So(err, ShouldBeNil)
  40. So(result, ShouldEqual, user1)
  41. })
  42. ldapAutherScenario("Given no existing grafana user", func(sc *scenarioContext) {
  43. ldapAuther := NewLdapAuthenticator(&LdapServerConf{
  44. LdapGroups: []*LdapGroupToOrgRole{
  45. {GroupDN: "cn=admin", OrgRole: "Admin"},
  46. {GroupDN: "cn=editor", OrgRole: "Editor"},
  47. {GroupDN: "*", OrgRole: "Viewer"},
  48. },
  49. })
  50. sc.userQueryReturns(nil)
  51. result, err := ldapAuther.GetGrafanaUserFor(&LdapUserInfo{
  52. Username: "torkelo",
  53. Email: "my@email.com",
  54. MemberOf: []string{"cn=editor"},
  55. })
  56. So(err, ShouldBeNil)
  57. Convey("Should create new user", func() {
  58. So(sc.createUserCmd.Login, ShouldEqual, "torkelo")
  59. So(sc.createUserCmd.Email, ShouldEqual, "my@email.com")
  60. })
  61. Convey("Should return new user", func() {
  62. So(result.Login, ShouldEqual, "torkelo")
  63. })
  64. })
  65. })
  66. Convey("When syncing ldap groups to grafana org roles", t, func() {
  67. ldapAutherScenario("given no current user orgs", func(sc *scenarioContext) {
  68. ldapAuther := NewLdapAuthenticator(&LdapServerConf{
  69. LdapGroups: []*LdapGroupToOrgRole{
  70. {GroupDN: "cn=users", OrgRole: "Admin"},
  71. },
  72. })
  73. sc.userOrgsQueryReturns([]*m.UserOrgDTO{})
  74. err := ldapAuther.SyncOrgRoles(&m.User{}, &LdapUserInfo{
  75. MemberOf: []string{"cn=users"},
  76. })
  77. Convey("Should create new org user", func() {
  78. So(err, ShouldBeNil)
  79. So(sc.addOrgUserCmd, ShouldNotBeNil)
  80. So(sc.addOrgUserCmd.Role, ShouldEqual, m.ROLE_ADMIN)
  81. })
  82. })
  83. ldapAutherScenario("given different current org role", func(sc *scenarioContext) {
  84. ldapAuther := NewLdapAuthenticator(&LdapServerConf{
  85. LdapGroups: []*LdapGroupToOrgRole{
  86. {GroupDN: "cn=users", OrgId: 1, OrgRole: "Admin"},
  87. },
  88. })
  89. sc.userOrgsQueryReturns([]*m.UserOrgDTO{{OrgId: 1, Role: m.ROLE_EDITOR}})
  90. err := ldapAuther.SyncOrgRoles(&m.User{}, &LdapUserInfo{
  91. MemberOf: []string{"cn=users"},
  92. })
  93. Convey("Should update org role", func() {
  94. So(err, ShouldBeNil)
  95. So(sc.updateOrgUserCmd, ShouldNotBeNil)
  96. So(sc.updateOrgUserCmd.Role, ShouldEqual, m.ROLE_ADMIN)
  97. })
  98. })
  99. ldapAutherScenario("given current org role is removed in ldap", func(sc *scenarioContext) {
  100. ldapAuther := NewLdapAuthenticator(&LdapServerConf{
  101. LdapGroups: []*LdapGroupToOrgRole{
  102. {GroupDN: "cn=users", OrgId: 1, OrgRole: "Admin"},
  103. },
  104. })
  105. sc.userOrgsQueryReturns([]*m.UserOrgDTO{{OrgId: 1, Role: m.ROLE_EDITOR}})
  106. err := ldapAuther.SyncOrgRoles(&m.User{}, &LdapUserInfo{
  107. MemberOf: []string{"cn=other"},
  108. })
  109. Convey("Should remove org role", func() {
  110. So(err, ShouldBeNil)
  111. So(sc.removeOrgUserCmd, ShouldNotBeNil)
  112. })
  113. })
  114. ldapAutherScenario("given org role is updated in config", func(sc *scenarioContext) {
  115. ldapAuther := NewLdapAuthenticator(&LdapServerConf{
  116. LdapGroups: []*LdapGroupToOrgRole{
  117. {GroupDN: "cn=admin", OrgId: 1, OrgRole: "Admin"},
  118. {GroupDN: "cn=users", OrgId: 1, OrgRole: "Viewer"},
  119. },
  120. })
  121. sc.userOrgsQueryReturns([]*m.UserOrgDTO{{OrgId: 1, Role: m.ROLE_EDITOR}})
  122. err := ldapAuther.SyncOrgRoles(&m.User{}, &LdapUserInfo{
  123. MemberOf: []string{"cn=users"},
  124. })
  125. Convey("Should update org role", func() {
  126. So(err, ShouldBeNil)
  127. So(sc.removeOrgUserCmd, ShouldBeNil)
  128. So(sc.updateOrgUserCmd, ShouldNotBeNil)
  129. })
  130. })
  131. ldapAutherScenario("given multiple matching ldap groups", func(sc *scenarioContext) {
  132. ldapAuther := NewLdapAuthenticator(&LdapServerConf{
  133. LdapGroups: []*LdapGroupToOrgRole{
  134. {GroupDN: "cn=admins", OrgId: 1, OrgRole: "Admin"},
  135. {GroupDN: "*", OrgId: 1, OrgRole: "Viewer"},
  136. },
  137. })
  138. sc.userOrgsQueryReturns([]*m.UserOrgDTO{{OrgId: 1, Role: m.ROLE_ADMIN}})
  139. err := ldapAuther.SyncOrgRoles(&m.User{}, &LdapUserInfo{
  140. MemberOf: []string{"cn=admins"},
  141. })
  142. Convey("Should take first match, and ignore subsequent matches", func() {
  143. So(err, ShouldBeNil)
  144. So(sc.updateOrgUserCmd, ShouldBeNil)
  145. })
  146. })
  147. ldapAutherScenario("given multiple matching ldap groups and no existing groups", func(sc *scenarioContext) {
  148. ldapAuther := NewLdapAuthenticator(&LdapServerConf{
  149. LdapGroups: []*LdapGroupToOrgRole{
  150. {GroupDN: "cn=admins", OrgId: 1, OrgRole: "Admin"},
  151. {GroupDN: "*", OrgId: 1, OrgRole: "Viewer"},
  152. },
  153. })
  154. sc.userOrgsQueryReturns([]*m.UserOrgDTO{})
  155. err := ldapAuther.SyncOrgRoles(&m.User{}, &LdapUserInfo{
  156. MemberOf: []string{"cn=admins"},
  157. })
  158. Convey("Should take first match, and ignore subsequent matches", func() {
  159. So(err, ShouldBeNil)
  160. So(sc.addOrgUserCmd.Role, ShouldEqual, m.ROLE_ADMIN)
  161. })
  162. })
  163. })
  164. Convey("When calling SyncSignedInUser", t, func() {
  165. mockLdapConnection := &mockLdapConn{}
  166. ldapAuther := NewLdapAuthenticator(
  167. &LdapServerConf{
  168. Host: "",
  169. RootCACert: "",
  170. LdapGroups: []*LdapGroupToOrgRole{
  171. {GroupDN: "*", OrgRole: "Admin"},
  172. },
  173. Attr: LdapAttributeMap{
  174. Username: "username",
  175. Surname: "surname",
  176. Email: "email",
  177. Name: "name",
  178. MemberOf: "memberof",
  179. },
  180. SearchBaseDNs: []string{"BaseDNHere"},
  181. },
  182. )
  183. dialCalled := false
  184. ldapDial = func(network, addr string) (ILdapConn, error) {
  185. dialCalled = true
  186. return mockLdapConnection, nil
  187. }
  188. entry := ldap.Entry{
  189. DN: "dn", Attributes: []*ldap.EntryAttribute{
  190. {Name: "username", Values: []string{"roelgerrits"}},
  191. {Name: "surname", Values: []string{"Gerrits"}},
  192. {Name: "email", Values: []string{"roel@test.com"}},
  193. {Name: "name", Values: []string{"Roel"}},
  194. {Name: "memberof", Values: []string{"admins"}},
  195. }}
  196. result := ldap.SearchResult{Entries: []*ldap.Entry{&entry}}
  197. mockLdapConnection.setSearchResult(&result)
  198. ldapAutherScenario("When ldapUser found call syncInfo and orgRoles", func(sc *scenarioContext) {
  199. // arrange
  200. signedInUser := &m.SignedInUser{
  201. Email: "roel@test.net",
  202. UserId: 1,
  203. Name: "Roel Gerrits",
  204. Login: "roelgerrits",
  205. }
  206. sc.userOrgsQueryReturns([]*m.UserOrgDTO{})
  207. // act
  208. syncErrResult := ldapAuther.SyncSignedInUser(signedInUser)
  209. // assert
  210. So(dialCalled, ShouldBeTrue)
  211. So(syncErrResult, ShouldBeNil)
  212. // User should be searched in ldap
  213. So(mockLdapConnection.searchCalled, ShouldBeTrue)
  214. // Info should be updated (email differs)
  215. So(sc.updateUserCmd.Email, ShouldEqual, "roel@test.com")
  216. // User should have admin privileges
  217. So(sc.addOrgUserCmd.UserId, ShouldEqual, 1)
  218. So(sc.addOrgUserCmd.Role, ShouldEqual, "Admin")
  219. })
  220. })
  221. }
  222. type mockLdapConn struct {
  223. result *ldap.SearchResult
  224. searchCalled bool
  225. }
  226. func (c *mockLdapConn) Bind(username, password string) error {
  227. return nil
  228. }
  229. func (c *mockLdapConn) Close() {}
  230. func (c *mockLdapConn) setSearchResult(result *ldap.SearchResult) {
  231. c.result = result
  232. }
  233. func (c *mockLdapConn) Search(*ldap.SearchRequest) (*ldap.SearchResult, error) {
  234. c.searchCalled = true
  235. return c.result, nil
  236. }
  237. func (c *mockLdapConn) StartTLS(*tls.Config) error {
  238. return nil
  239. }
  240. func ldapAutherScenario(desc string, fn scenarioFunc) {
  241. Convey(desc, func() {
  242. defer bus.ClearBusHandlers()
  243. sc := &scenarioContext{}
  244. bus.AddHandler("test", func(cmd *m.CreateUserCommand) error {
  245. sc.createUserCmd = cmd
  246. sc.createUserCmd.Result = m.User{Login: cmd.Login}
  247. return nil
  248. })
  249. bus.AddHandler("test", func(cmd *m.AddOrgUserCommand) error {
  250. sc.addOrgUserCmd = cmd
  251. return nil
  252. })
  253. bus.AddHandler("test", func(cmd *m.UpdateOrgUserCommand) error {
  254. sc.updateOrgUserCmd = cmd
  255. return nil
  256. })
  257. bus.AddHandler("test", func(cmd *m.RemoveOrgUserCommand) error {
  258. sc.removeOrgUserCmd = cmd
  259. return nil
  260. })
  261. bus.AddHandler("test", func(cmd *m.UpdateUserCommand) error {
  262. sc.updateUserCmd = cmd
  263. return nil
  264. })
  265. fn(sc)
  266. })
  267. }
  268. type scenarioContext struct {
  269. createUserCmd *m.CreateUserCommand
  270. addOrgUserCmd *m.AddOrgUserCommand
  271. updateOrgUserCmd *m.UpdateOrgUserCommand
  272. removeOrgUserCmd *m.RemoveOrgUserCommand
  273. updateUserCmd *m.UpdateUserCommand
  274. }
  275. func (sc *scenarioContext) userQueryReturns(user *m.User) {
  276. bus.AddHandler("test", func(query *m.GetUserByLoginQuery) error {
  277. if user == nil {
  278. return m.ErrUserNotFound
  279. } else {
  280. query.Result = user
  281. return nil
  282. }
  283. })
  284. }
  285. func (sc *scenarioContext) userOrgsQueryReturns(orgs []*m.UserOrgDTO) {
  286. bus.AddHandler("test", func(query *m.GetUserOrgListQuery) error {
  287. query.Result = orgs
  288. return nil
  289. })
  290. }
  291. type scenarioFunc func(c *scenarioContext)