You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

auth_ldap_test.go 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. // Copyright 2018 The Gitea Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package integrations
  5. import (
  6. "context"
  7. "net/http"
  8. "os"
  9. "strings"
  10. "testing"
  11. "code.gitea.io/gitea/models"
  12. "code.gitea.io/gitea/models/db"
  13. "code.gitea.io/gitea/models/organization"
  14. "code.gitea.io/gitea/models/unittest"
  15. user_model "code.gitea.io/gitea/models/user"
  16. "code.gitea.io/gitea/modules/translation/i18n"
  17. "code.gitea.io/gitea/services/auth"
  18. "github.com/stretchr/testify/assert"
  19. )
  20. type ldapUser struct {
  21. UserName string
  22. Password string
  23. FullName string
  24. Email string
  25. OtherEmails []string
  26. IsAdmin bool
  27. IsRestricted bool
  28. SSHKeys []string
  29. }
  30. var gitLDAPUsers = []ldapUser{
  31. {
  32. UserName: "professor",
  33. Password: "professor",
  34. FullName: "Hubert Farnsworth",
  35. Email: "professor@planetexpress.com",
  36. OtherEmails: []string{"hubert@planetexpress.com"},
  37. IsAdmin: true,
  38. },
  39. {
  40. UserName: "hermes",
  41. Password: "hermes",
  42. FullName: "Conrad Hermes",
  43. Email: "hermes@planetexpress.com",
  44. SSHKeys: []string{
  45. "SHA256:qLY06smKfHoW/92yXySpnxFR10QFrLdRjf/GNPvwcW8",
  46. "SHA256:QlVTuM5OssDatqidn2ffY+Lc4YA5Fs78U+0KOHI51jQ",
  47. "SHA256:DXdeUKYOJCSSmClZuwrb60hUq7367j4fA+udNC3FdRI",
  48. },
  49. IsAdmin: true,
  50. },
  51. {
  52. UserName: "fry",
  53. Password: "fry",
  54. FullName: "Philip Fry",
  55. Email: "fry@planetexpress.com",
  56. },
  57. {
  58. UserName: "leela",
  59. Password: "leela",
  60. FullName: "Leela Turanga",
  61. Email: "leela@planetexpress.com",
  62. IsRestricted: true,
  63. },
  64. {
  65. UserName: "bender",
  66. Password: "bender",
  67. FullName: "Bender Rodríguez",
  68. Email: "bender@planetexpress.com",
  69. },
  70. }
  71. var otherLDAPUsers = []ldapUser{
  72. {
  73. UserName: "zoidberg",
  74. Password: "zoidberg",
  75. FullName: "John Zoidberg",
  76. Email: "zoidberg@planetexpress.com",
  77. },
  78. {
  79. UserName: "amy",
  80. Password: "amy",
  81. FullName: "Amy Kroker",
  82. Email: "amy@planetexpress.com",
  83. },
  84. }
  85. func skipLDAPTests() bool {
  86. return os.Getenv("TEST_LDAP") != "1"
  87. }
  88. func getLDAPServerHost() string {
  89. host := os.Getenv("TEST_LDAP_HOST")
  90. if len(host) == 0 {
  91. host = "ldap"
  92. }
  93. return host
  94. }
  95. func addAuthSourceLDAP(t *testing.T, sshKeyAttribute string, groupMapParams ...string) {
  96. groupTeamMapRemoval := "off"
  97. groupTeamMap := ""
  98. if len(groupMapParams) == 2 {
  99. groupTeamMapRemoval = groupMapParams[0]
  100. groupTeamMap = groupMapParams[1]
  101. }
  102. session := loginUser(t, "user1")
  103. csrf := GetCSRF(t, session, "/admin/auths/new")
  104. req := NewRequestWithValues(t, "POST", "/admin/auths/new", map[string]string{
  105. "_csrf": csrf,
  106. "type": "2",
  107. "name": "ldap",
  108. "host": getLDAPServerHost(),
  109. "port": "389",
  110. "bind_dn": "uid=gitea,ou=service,dc=planetexpress,dc=com",
  111. "bind_password": "password",
  112. "user_base": "ou=people,dc=planetexpress,dc=com",
  113. "filter": "(&(objectClass=inetOrgPerson)(memberOf=cn=git,ou=people,dc=planetexpress,dc=com)(uid=%s))",
  114. "admin_filter": "(memberOf=cn=admin_staff,ou=people,dc=planetexpress,dc=com)",
  115. "restricted_filter": "(uid=leela)",
  116. "attribute_username": "uid",
  117. "attribute_name": "givenName",
  118. "attribute_surname": "sn",
  119. "attribute_mail": "mail",
  120. "attribute_ssh_public_key": sshKeyAttribute,
  121. "is_sync_enabled": "on",
  122. "is_active": "on",
  123. "groups_enabled": "on",
  124. "group_dn": "ou=people,dc=planetexpress,dc=com",
  125. "group_member_uid": "member",
  126. "group_team_map": groupTeamMap,
  127. "group_team_map_removal": groupTeamMapRemoval,
  128. "user_uid": "DN",
  129. })
  130. session.MakeRequest(t, req, http.StatusSeeOther)
  131. }
  132. func TestLDAPUserSignin(t *testing.T) {
  133. if skipLDAPTests() {
  134. t.Skip()
  135. return
  136. }
  137. defer prepareTestEnv(t)()
  138. addAuthSourceLDAP(t, "")
  139. u := gitLDAPUsers[0]
  140. session := loginUserWithPassword(t, u.UserName, u.Password)
  141. req := NewRequest(t, "GET", "/user/settings")
  142. resp := session.MakeRequest(t, req, http.StatusOK)
  143. htmlDoc := NewHTMLParser(t, resp.Body)
  144. assert.Equal(t, u.UserName, htmlDoc.GetInputValueByName("name"))
  145. assert.Equal(t, u.FullName, htmlDoc.GetInputValueByName("full_name"))
  146. assert.Equal(t, u.Email, htmlDoc.Find(`label[for="email"]`).Siblings().First().Text())
  147. }
  148. func TestLDAPAuthChange(t *testing.T) {
  149. defer prepareTestEnv(t)()
  150. addAuthSourceLDAP(t, "")
  151. session := loginUser(t, "user1")
  152. req := NewRequest(t, "GET", "/admin/auths")
  153. resp := session.MakeRequest(t, req, http.StatusOK)
  154. doc := NewHTMLParser(t, resp.Body)
  155. href, exists := doc.Find("table.table td a").Attr("href")
  156. if !exists {
  157. assert.True(t, exists, "No authentication source found")
  158. return
  159. }
  160. req = NewRequest(t, "GET", href)
  161. resp = session.MakeRequest(t, req, http.StatusOK)
  162. doc = NewHTMLParser(t, resp.Body)
  163. csrf := doc.GetCSRF()
  164. host, _ := doc.Find(`input[name="host"]`).Attr("value")
  165. assert.Equal(t, host, getLDAPServerHost())
  166. binddn, _ := doc.Find(`input[name="bind_dn"]`).Attr("value")
  167. assert.Equal(t, binddn, "uid=gitea,ou=service,dc=planetexpress,dc=com")
  168. req = NewRequestWithValues(t, "POST", href, map[string]string{
  169. "_csrf": csrf,
  170. "type": "2",
  171. "name": "ldap",
  172. "host": getLDAPServerHost(),
  173. "port": "389",
  174. "bind_dn": "uid=gitea,ou=service,dc=planetexpress,dc=com",
  175. "bind_password": "password",
  176. "user_base": "ou=people,dc=planetexpress,dc=com",
  177. "filter": "(&(objectClass=inetOrgPerson)(memberOf=cn=git,ou=people,dc=planetexpress,dc=com)(uid=%s))",
  178. "admin_filter": "(memberOf=cn=admin_staff,ou=people,dc=planetexpress,dc=com)",
  179. "restricted_filter": "(uid=leela)",
  180. "attribute_username": "uid",
  181. "attribute_name": "givenName",
  182. "attribute_surname": "sn",
  183. "attribute_mail": "mail",
  184. "attribute_ssh_public_key": "",
  185. "is_sync_enabled": "on",
  186. "is_active": "on",
  187. })
  188. session.MakeRequest(t, req, http.StatusSeeOther)
  189. req = NewRequest(t, "GET", href)
  190. resp = session.MakeRequest(t, req, http.StatusOK)
  191. doc = NewHTMLParser(t, resp.Body)
  192. host, _ = doc.Find(`input[name="host"]`).Attr("value")
  193. assert.Equal(t, host, getLDAPServerHost())
  194. binddn, _ = doc.Find(`input[name="bind_dn"]`).Attr("value")
  195. assert.Equal(t, binddn, "uid=gitea,ou=service,dc=planetexpress,dc=com")
  196. }
  197. func TestLDAPUserSync(t *testing.T) {
  198. if skipLDAPTests() {
  199. t.Skip()
  200. return
  201. }
  202. defer prepareTestEnv(t)()
  203. addAuthSourceLDAP(t, "")
  204. auth.SyncExternalUsers(context.Background(), true)
  205. session := loginUser(t, "user1")
  206. // Check if users exists
  207. for _, u := range gitLDAPUsers {
  208. req := NewRequest(t, "GET", "/admin/users?q="+u.UserName)
  209. resp := session.MakeRequest(t, req, http.StatusOK)
  210. htmlDoc := NewHTMLParser(t, resp.Body)
  211. tr := htmlDoc.doc.Find("table.table tbody tr")
  212. if !assert.True(t, tr.Length() == 1) {
  213. continue
  214. }
  215. tds := tr.Find("td")
  216. if !assert.True(t, tds.Length() > 0) {
  217. continue
  218. }
  219. assert.Equal(t, u.UserName, strings.TrimSpace(tds.Find("td:nth-child(2) a").Text()))
  220. assert.Equal(t, u.Email, strings.TrimSpace(tds.Find("td:nth-child(3) span").Text()))
  221. if u.IsAdmin {
  222. assert.True(t, tds.Find("td:nth-child(5) svg").HasClass("octicon-check"))
  223. } else {
  224. assert.True(t, tds.Find("td:nth-child(5) svg").HasClass("octicon-x"))
  225. }
  226. if u.IsRestricted {
  227. assert.True(t, tds.Find("td:nth-child(6) svg").HasClass("octicon-check"))
  228. } else {
  229. assert.True(t, tds.Find("td:nth-child(6) svg").HasClass("octicon-x"))
  230. }
  231. }
  232. // Check if no users exist
  233. for _, u := range otherLDAPUsers {
  234. req := NewRequest(t, "GET", "/admin/users?q="+u.UserName)
  235. resp := session.MakeRequest(t, req, http.StatusOK)
  236. htmlDoc := NewHTMLParser(t, resp.Body)
  237. tr := htmlDoc.doc.Find("table.table tbody tr")
  238. assert.True(t, tr.Length() == 0)
  239. }
  240. }
  241. func TestLDAPUserSigninFailed(t *testing.T) {
  242. if skipLDAPTests() {
  243. t.Skip()
  244. return
  245. }
  246. defer prepareTestEnv(t)()
  247. addAuthSourceLDAP(t, "")
  248. u := otherLDAPUsers[0]
  249. testLoginFailed(t, u.UserName, u.Password, i18n.Tr("en", "form.username_password_incorrect"))
  250. }
  251. func TestLDAPUserSSHKeySync(t *testing.T) {
  252. if skipLDAPTests() {
  253. t.Skip()
  254. return
  255. }
  256. defer prepareTestEnv(t)()
  257. addAuthSourceLDAP(t, "sshPublicKey")
  258. auth.SyncExternalUsers(context.Background(), true)
  259. // Check if users has SSH keys synced
  260. for _, u := range gitLDAPUsers {
  261. if len(u.SSHKeys) == 0 {
  262. continue
  263. }
  264. session := loginUserWithPassword(t, u.UserName, u.Password)
  265. req := NewRequest(t, "GET", "/user/settings/keys")
  266. resp := session.MakeRequest(t, req, http.StatusOK)
  267. htmlDoc := NewHTMLParser(t, resp.Body)
  268. divs := htmlDoc.doc.Find(".key.list .print.meta")
  269. syncedKeys := make([]string, divs.Length())
  270. for i := 0; i < divs.Length(); i++ {
  271. syncedKeys[i] = strings.TrimSpace(divs.Eq(i).Text())
  272. }
  273. assert.ElementsMatch(t, u.SSHKeys, syncedKeys, "Unequal number of keys synchronized for user: %s", u.UserName)
  274. }
  275. }
  276. func TestLDAPGroupTeamSyncAddMember(t *testing.T) {
  277. if skipLDAPTests() {
  278. t.Skip()
  279. return
  280. }
  281. defer prepareTestEnv(t)()
  282. addAuthSourceLDAP(t, "", "on", `{"cn=ship_crew,ou=people,dc=planetexpress,dc=com":{"org26": ["team11"]},"cn=admin_staff,ou=people,dc=planetexpress,dc=com": {"non-existent": ["non-existent"]}}`)
  283. org, err := organization.GetOrgByName("org26")
  284. assert.NoError(t, err)
  285. team, err := organization.GetTeam(db.DefaultContext, org.ID, "team11")
  286. assert.NoError(t, err)
  287. auth.SyncExternalUsers(context.Background(), true)
  288. for _, gitLDAPUser := range gitLDAPUsers {
  289. user := unittest.AssertExistsAndLoadBean(t, &user_model.User{
  290. Name: gitLDAPUser.UserName,
  291. }).(*user_model.User)
  292. usersOrgs, err := organization.FindOrgs(organization.FindOrgOptions{
  293. UserID: user.ID,
  294. IncludePrivate: true,
  295. })
  296. assert.NoError(t, err)
  297. allOrgTeams, err := organization.GetUserOrgTeams(db.DefaultContext, org.ID, user.ID)
  298. assert.NoError(t, err)
  299. if user.Name == "fry" || user.Name == "leela" || user.Name == "bender" {
  300. // assert members of LDAP group "cn=ship_crew" are added to mapped teams
  301. assert.Equal(t, len(usersOrgs), 1, "User [%s] should be member of one organization", user.Name)
  302. assert.Equal(t, usersOrgs[0].Name, "org26", "Membership should be added to the right organization")
  303. isMember, err := organization.IsTeamMember(db.DefaultContext, usersOrgs[0].ID, team.ID, user.ID)
  304. assert.NoError(t, err)
  305. assert.True(t, isMember, "Membership should be added to the right team")
  306. err = models.RemoveTeamMember(team, user.ID)
  307. assert.NoError(t, err)
  308. err = models.RemoveOrgUser(usersOrgs[0].ID, user.ID)
  309. assert.NoError(t, err)
  310. } else {
  311. // assert members of LDAP group "cn=admin_staff" keep initial team membership since mapped team does not exist
  312. assert.Empty(t, usersOrgs, "User should be member of no organization")
  313. isMember, err := organization.IsTeamMember(db.DefaultContext, org.ID, team.ID, user.ID)
  314. assert.NoError(t, err)
  315. assert.False(t, isMember, "User should no be added to this team")
  316. assert.Empty(t, allOrgTeams, "User should not be added to any team")
  317. }
  318. }
  319. }
  320. func TestLDAPGroupTeamSyncRemoveMember(t *testing.T) {
  321. if skipLDAPTests() {
  322. t.Skip()
  323. return
  324. }
  325. defer prepareTestEnv(t)()
  326. addAuthSourceLDAP(t, "", "on", `{"cn=dispatch,ou=people,dc=planetexpress,dc=com": {"org26": ["team11"]}}`)
  327. org, err := organization.GetOrgByName("org26")
  328. assert.NoError(t, err)
  329. team, err := organization.GetTeam(db.DefaultContext, org.ID, "team11")
  330. assert.NoError(t, err)
  331. loginUserWithPassword(t, gitLDAPUsers[0].UserName, gitLDAPUsers[0].Password)
  332. user := unittest.AssertExistsAndLoadBean(t, &user_model.User{
  333. Name: gitLDAPUsers[0].UserName,
  334. }).(*user_model.User)
  335. err = organization.AddOrgUser(org.ID, user.ID)
  336. assert.NoError(t, err)
  337. err = models.AddTeamMember(team, user.ID)
  338. assert.NoError(t, err)
  339. isMember, err := organization.IsOrganizationMember(db.DefaultContext, org.ID, user.ID)
  340. assert.NoError(t, err)
  341. assert.True(t, isMember, "User should be member of this organization")
  342. isMember, err = organization.IsTeamMember(db.DefaultContext, org.ID, team.ID, user.ID)
  343. assert.NoError(t, err)
  344. assert.True(t, isMember, "User should be member of this team")
  345. // assert team member "professor" gets removed from org26 team11
  346. loginUserWithPassword(t, gitLDAPUsers[0].UserName, gitLDAPUsers[0].Password)
  347. isMember, err = organization.IsOrganizationMember(db.DefaultContext, org.ID, user.ID)
  348. assert.NoError(t, err)
  349. assert.False(t, isMember, "User membership should have been removed from organization")
  350. isMember, err = organization.IsTeamMember(db.DefaultContext, org.ID, team.ID, user.ID)
  351. assert.NoError(t, err)
  352. assert.False(t, isMember, "User membership should have been removed from team")
  353. }
  354. // Login should work even if Team Group Map contains a broken JSON
  355. func TestBrokenLDAPMapUserSignin(t *testing.T) {
  356. if skipLDAPTests() {
  357. t.Skip()
  358. return
  359. }
  360. defer prepareTestEnv(t)()
  361. addAuthSourceLDAP(t, "", "on", `{"NOT_A_VALID_JSON"["MISSING_DOUBLE_POINT"]}`)
  362. u := gitLDAPUsers[0]
  363. session := loginUserWithPassword(t, u.UserName, u.Password)
  364. req := NewRequest(t, "GET", "/user/settings")
  365. resp := session.MakeRequest(t, req, http.StatusOK)
  366. htmlDoc := NewHTMLParser(t, resp.Body)
  367. assert.Equal(t, u.UserName, htmlDoc.GetInputValueByName("name"))
  368. assert.Equal(t, u.FullName, htmlDoc.GetInputValueByName("full_name"))
  369. assert.Equal(t, u.Email, htmlDoc.Find(`label[for="email"]`).Siblings().First().Text())
  370. }