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.

saml_test.go 4.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. // Copyright 2023 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package integration
  4. import (
  5. "crypto/tls"
  6. "crypto/x509"
  7. "fmt"
  8. "io"
  9. "net/http"
  10. "net/http/cookiejar"
  11. "net/url"
  12. "os"
  13. "regexp"
  14. "strings"
  15. "testing"
  16. "time"
  17. "code.gitea.io/gitea/models/auth"
  18. "code.gitea.io/gitea/models/db"
  19. user_model "code.gitea.io/gitea/models/user"
  20. "code.gitea.io/gitea/modules/setting"
  21. "code.gitea.io/gitea/modules/test"
  22. "code.gitea.io/gitea/services/auth/source/saml"
  23. "code.gitea.io/gitea/tests"
  24. "github.com/stretchr/testify/assert"
  25. )
  26. func TestSAMLRegistration(t *testing.T) {
  27. defer tests.PrepareTestEnv(t)()
  28. samlURL := "localhost:8080"
  29. if os.Getenv("CI") == "" || !setting.Database.Type.IsPostgreSQL() {
  30. // Make it possible to run tests against a local simplesaml instance
  31. samlURL = os.Getenv("TEST_SIMPLESAML_URL")
  32. if samlURL == "" {
  33. t.Skip("TEST_SIMPLESAML_URL not set and not running in CI")
  34. return
  35. }
  36. }
  37. privateKey, cert, err := saml.GenerateSAMLSPKeypair()
  38. assert.NoError(t, err)
  39. // verify that the keypair can be parsed
  40. keyPair, err := tls.X509KeyPair([]byte(cert), []byte(privateKey))
  41. assert.NoError(t, err)
  42. keyPair.Leaf, err = x509.ParseCertificate(keyPair.Certificate[0])
  43. assert.NoError(t, err)
  44. assert.NoError(t, auth.CreateSource(db.DefaultContext, &auth.Source{
  45. Type: auth.SAML,
  46. Name: "test-sp",
  47. IsActive: true,
  48. IsSyncEnabled: false,
  49. Cfg: &saml.Source{
  50. IdentityProviderMetadata: "",
  51. IdentityProviderMetadataURL: fmt.Sprintf("http://%s/simplesaml/saml2/idp/metadata.php", samlURL),
  52. InsecureSkipAssertionSignatureValidation: false,
  53. NameIDFormat: 4,
  54. ServiceProviderCertificate: "", // SimpleSAMLPhp requires that the SP certificate be specified in the server configuration rather than SP metadata
  55. ServiceProviderPrivateKey: "",
  56. EmailAssertionKey: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress",
  57. NameAssertionKey: "http://schemas.xmlsoap.org/claims/CommonName",
  58. UsernameAssertionKey: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name",
  59. IconURL: "",
  60. },
  61. }))
  62. // check the saml metadata url
  63. req := NewRequest(t, "GET", "/user/saml/test-sp/metadata")
  64. MakeRequest(t, req, http.StatusOK)
  65. req = NewRequest(t, "GET", "/user/saml/test-sp")
  66. resp := MakeRequest(t, req, http.StatusTemporaryRedirect)
  67. jar, err := cookiejar.New(nil)
  68. assert.NoError(t, err)
  69. client := http.Client{
  70. Timeout: 30 * time.Second,
  71. Jar: jar,
  72. }
  73. httpReq, err := http.NewRequest("GET", test.RedirectURL(resp), nil)
  74. assert.NoError(t, err)
  75. var formRedirectURL *url.URL
  76. client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
  77. // capture the redirected destination to use in POST request
  78. formRedirectURL = req.URL
  79. return nil
  80. }
  81. res, err := client.Do(httpReq)
  82. client.CheckRedirect = nil
  83. assert.NoError(t, err)
  84. assert.Equal(t, http.StatusOK, res.StatusCode)
  85. assert.NotNil(t, formRedirectURL)
  86. form := url.Values{
  87. "username": {"user1"},
  88. "password": {"user1pass"},
  89. }
  90. httpReq, err = http.NewRequest("POST", formRedirectURL.String(), strings.NewReader(form.Encode()))
  91. assert.NoError(t, err)
  92. httpReq.Header.Add("Content-Type", "application/x-www-form-urlencoded")
  93. res, err = client.Do(httpReq)
  94. assert.NoError(t, err)
  95. assert.Equal(t, http.StatusOK, res.StatusCode)
  96. body, err := io.ReadAll(res.Body)
  97. assert.NoError(t, err)
  98. samlResMatcher := regexp.MustCompile(`<input.*?name="SAMLResponse".*?value="([^"]+)".*?>`)
  99. matches := samlResMatcher.FindStringSubmatch(string(body))
  100. assert.Len(t, matches, 2)
  101. assert.NoError(t, res.Body.Close())
  102. session := emptyTestSession(t)
  103. req = NewRequestWithValues(t, "POST", "/user/saml/test-sp/acs", map[string]string{
  104. "SAMLResponse": matches[1],
  105. })
  106. resp = session.MakeRequest(t, req, http.StatusSeeOther)
  107. assert.Equal(t, test.RedirectURL(resp), "/user/link_account")
  108. csrf := GetCSRF(t, session, test.RedirectURL(resp))
  109. // link the account
  110. req = NewRequestWithValues(t, "POST", "/user/link_account_signup", map[string]string{
  111. "_csrf": csrf,
  112. "user_name": "samluser",
  113. "email": "saml@example.com",
  114. })
  115. resp = session.MakeRequest(t, req, http.StatusSeeOther)
  116. assert.Equal(t, test.RedirectURL(resp), "/")
  117. // verify that the user was created
  118. u, err := user_model.GetUserByEmail(db.DefaultContext, "saml@example.com")
  119. assert.NoError(t, err)
  120. assert.NotNil(t, u)
  121. assert.Equal(t, "samluser", u.Name)
  122. }