Revert "Support SAML authentication (#25165)" (#29358)

This reverts #25165 (5bb8d1924d), as there
was a chance some important reviews got missed.

so after reverting this patch it will be resubmitted for reviewing again

https://github.com/go-gitea/gitea/pull/25165#issuecomment-1960670242

temporary Open #5512 again
This commit is contained in:
6543 authored and GitHub committed 2024-02-24 12:18:49 +08:00
1 parent 875f5ea6d8
commit 4ba642d07d
37 files changed
+69 -1440

No files matched your search

-8
View File
@@ -37,14 +37,6 @@ jobs:
MINIO_ROOT_PASSWORD: 12345678 MINIO_ROOT_PASSWORD: 12345678
ports: ports:
- "9000:9000" - "9000:9000"
simplesaml:
image: allspice/simple-saml
ports:
- "8080:8080"
env:
SIMPLESAMLPHP_SP_ENTITY_ID: http://localhost:3002/user/saml/test-sp/metadata
SIMPLESAMLPHP_SP_ASSERTION_CONSUMER_SERVICE: http://localhost:3002/user/saml/test-sp/acs
SIMPLESAMLPHP_SP_SINGLE_LOGOUT_SERVICE: http://localhost:3002/user/saml/test-sp/acs
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-go@v5 - uses: actions/setup-go@v5
-25
View File
@@ -124,11 +124,6 @@
"path": "github.com/aymerick/douceur/LICENSE", "path": "github.com/aymerick/douceur/LICENSE",
"licenseText": "The MIT License (MIT)\n\nCopyright (c) 2015 Aymerick JEHANNE\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n" "licenseText": "The MIT License (MIT)\n\nCopyright (c) 2015 Aymerick JEHANNE\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n"
}, },
{
"name": "github.com/beevik/etree",
"path": "github.com/beevik/etree/LICENSE",
"licenseText": "Copyright 2015-2019 Brett Vickers. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions\nare met:\n\n 1. Redistributions of source code must retain the above copyright\n notice, this list of conditions and the following disclaimer.\n\n 2. Redistributions in binary form must reproduce the above copyright\n notice, this list of conditions and the following disclaimer in the\n documentation and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER ``AS IS'' AND ANY\nEXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER OR\nCONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\nEXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\nPROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\nPROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\nOF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
},
{ {
"name": "github.com/beorn7/perks/quantile", "name": "github.com/beorn7/perks/quantile",
"path": "github.com/beorn7/perks/quantile/LICENSE", "path": "github.com/beorn7/perks/quantile/LICENSE",
@@ -644,11 +639,6 @@
"path": "github.com/jhillyerd/enmime/LICENSE", "path": "github.com/jhillyerd/enmime/LICENSE",
"licenseText": "The MIT License (MIT)\n\nCopyright (c) 2012-2016 James Hillyerd, All Rights Reserved\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n" "licenseText": "The MIT License (MIT)\n\nCopyright (c) 2012-2016 James Hillyerd, All Rights Reserved\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
}, },
{
"name": "github.com/jonboulle/clockwork",
"path": "github.com/jonboulle/clockwork/LICENSE",
"licenseText": "Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under this License for that Work shall terLine truncated
},
{ {
"name": "github.com/josharian/intern", "name": "github.com/josharian/intern",
"path": "github.com/josharian/intern/license.md", "path": "github.com/josharian/intern/license.md",
@@ -729,11 +719,6 @@
"path": "github.com/markbates/goth/LICENSE.txt", "path": "github.com/markbates/goth/LICENSE.txt",
"licenseText": "Copyright (c) 2014 Mark Bates\n\nMIT License\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n" "licenseText": "Copyright (c) 2014 Mark Bates\n\nMIT License\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
}, },
{
"name": "github.com/mattermost/xml-roundtrip-validator",
"path": "github.com/mattermost/xml-roundtrip-validator/LICENSE.txt",
"licenseText": " Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under thiLine truncated
},
{ {
"name": "github.com/mattn/go-colorable", "name": "github.com/mattn/go-colorable",
"path": "github.com/mattn/go-colorable/LICENSE", "path": "github.com/mattn/go-colorable/LICENSE",
@@ -919,16 +904,6 @@
"path": "github.com/rs/xid/LICENSE", "path": "github.com/rs/xid/LICENSE",
"licenseText": "Copyright (c) 2015 Olivier Poitrey \u003crs@dailymotion.com\u003e\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is furnished\nto do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n" "licenseText": "Copyright (c) 2015 Olivier Poitrey \u003crs@dailymotion.com\u003e\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is furnished\nto do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
}, },
{
"name": "github.com/russellhaering/gosaml2",
"path": "github.com/russellhaering/gosaml2/LICENSE",
"licenseText": "\n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under tLine truncated
},
{
"name": "github.com/russellhaering/goxmldsig",
"path": "github.com/russellhaering/goxmldsig/LICENSE",
"licenseText": "\n Apache License\n Version 2.0, January 2004\n http://www.apache.org/licenses/\n\n TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n 1. Definitions.\n\n \"License\" shall mean the terms and conditions for use, reproduction,\n and distribution as defined by Sections 1 through 9 of this document.\n\n \"Licensor\" shall mean the copyright owner or entity authorized by\n the copyright owner that is granting the License.\n\n \"Legal Entity\" shall mean the union of the acting entity and all\n other entities that control, are controlled by, or are under common\n control with that entity. For the purposes of this definition,\n \"control\" means (i) the power, direct or indirect, to cause the\n direction or management of such entity, whether by contract or\n otherwise, or (ii) ownership of fifty percent (50%) or more of the\n outstanding shares, or (iii) beneficial ownership of such entity.\n\n \"You\" (or \"Your\") shall mean an individual or Legal Entity\n exercising permissions granted by this License.\n\n \"Source\" form shall mean the preferred form for making modifications,\n including but not limited to software source code, documentation\n source, and configuration files.\n\n \"Object\" form shall mean any form resulting from mechanical\n transformation or translation of a Source form, including but\n not limited to compiled object code, generated documentation,\n and conversions to other media types.\n\n \"Work\" shall mean the work of authorship, whether in Source or\n Object form, made available under the License, as indicated by a\n copyright notice that is included in or attached to the work\n (an example is provided in the Appendix below).\n\n \"Derivative Works\" shall mean any work, whether in Source or Object\n form, that is based on (or derived from) the Work and for which the\n editorial revisions, annotations, elaborations, or other modifications\n represent, as a whole, an original work of authorship. For the purposes\n of this License, Derivative Works shall not include works that remain\n separable from, or merely link (or bind by name) to the interfaces of,\n the Work and Derivative Works thereof.\n\n \"Contribution\" shall mean any work of authorship, including\n the original version of the Work and any modifications or additions\n to that Work or Derivative Works thereof, that is intentionally\n submitted to Licensor for inclusion in the Work by the copyright owner\n or by an individual or Legal Entity authorized to submit on behalf of\n the copyright owner. For the purposes of this definition, \"submitted\"\n means any form of electronic, verbal, or written communication sent\n to the Licensor or its representatives, including but not limited to\n communication on electronic mailing lists, source code control systems,\n and issue tracking systems that are managed by, or on behalf of, the\n Licensor for the purpose of discussing and improving the Work, but\n excluding communication that is conspicuously marked or otherwise\n designated in writing by the copyright owner as \"Not a Contribution.\"\n\n \"Contributor\" shall mean Licensor and any individual or Legal Entity\n on behalf of whom a Contribution has been received by Licensor and\n subsequently incorporated within the Work.\n\n 2. Grant of Copyright License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n copyright license to reproduce, prepare Derivative Works of,\n publicly display, publicly perform, sublicense, and distribute the\n Work and such Derivative Works in Source or Object form.\n\n 3. Grant of Patent License. Subject to the terms and conditions of\n this License, each Contributor hereby grants to You a perpetual,\n worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n (except as stated in this section) patent license to make, have made,\n use, offer to sell, sell, import, and otherwise transfer the Work,\n where such license applies only to those patent claims licensable\n by such Contributor that are necessarily infringed by their\n Contribution(s) alone or by combination of their Contribution(s)\n with the Work to which such Contribution(s) was submitted. If You\n institute patent litigation against any entity (including a\n cross-claim or counterclaim in a lawsuit) alleging that the Work\n or a Contribution incorporated within the Work constitutes direct\n or contributory patent infringement, then any patent licenses\n granted to You under tLine truncated
},
{ {
"name": "github.com/russross/blackfriday/v2", "name": "github.com/russross/blackfriday/v2",
"path": "github.com/russross/blackfriday/v2/LICENSE.txt", "path": "github.com/russross/blackfriday/v2/LICENSE.txt",
@@ -349,72 +349,3 @@ If set `ENABLE_REVERSE_PROXY_FULL_NAME=true`, a user full name expected in `X-WE
You can also limit the reverse proxy's IP address range with `REVERSE_PROXY_TRUSTED_PROXIES` which default value is `127.0.0.0/8,::1/128`. By `REVERSE_PROXY_LIMIT`, you can limit trusted proxies level. You can also limit the reverse proxy's IP address range with `REVERSE_PROXY_TRUSTED_PROXIES` which default value is `127.0.0.0/8,::1/128`. By `REVERSE_PROXY_LIMIT`, you can limit trusted proxies level.
Notice: Reverse Proxy Auth doesn't support the API. You still need an access token or basic auth to make API requests. Notice: Reverse Proxy Auth doesn't support the API. You still need an access token or basic auth to make API requests.
## SAML
### Configuring Gitea as a SAML 2.0 Service Provider
- Navigate to `Site Administration > Identity & Access > Authentication Sources`.
- Click the `Add Authentication Source` button.
- Select `SAML` as the authentication type.
#### Features Not Yet Supported
Currently, auto-registration is not supported for SAML. During the external account linking process the user will be prompted to set a username and email address or link to an existing account.
SAML group mapping is not supported.
#### Settings
- `Authentication Name` **(required)**
- The name of this authentication source (appears in the Gitea ACS and metadata URLs)
- `SAML NameID Format` **(required)**
- This specifies how Identity Provider (IdP) users are mapped to Gitea users. This option will be provider specific.
- `Icon URL` (optional)
- URL of an icon to display on the Sign-In page for this authentication source.
- `[Insecure] Skip Assertion Signature Validation` (optional)
- This option is not recommended and disables integrity verification of IdP SAML assertions.
- `Identity Provider Metadata URL` (optional if XML set)
- The URL of the IdP metadata endpoint.
- This field must be set if `Identity Provider Metadata XML` is left blank.
- `Identity Provider Metadata XML` (optional if URL set)
- The XML returned by the IdP metadata endpoint.
- This field must be set if `Identity Provider Metadata URL` is left blank.
- `Service Provider Certificate` (optional)
- X.509-formatted certificate (with `Service Provider Private Key`) used for signing SAML requests.
- A certificate will be generated if this field is left blank.
- `Service Provider Private Key` (optional)
- DSA/RSA private key (with `Service Provider Certificate`) used for signing SAML requests.
- A private key will be generated if this field is left blank.
- `Email Assertion Key` (optional)
- The SAML assertion key used for the IdP user's email (depends on provider configuration).
- `Name Assertion Key` (optional)
- The SAML assertion key used for the IdP user's nickname (depends on provider configuration).
- `Username Assertion Key` (optional)
- The SAML assertion key used for the IdP user's username (depends on provider configuration).
### Configuring a SAML 2.0 Identity Provider to use Gitea
- The service provider assertion consumer service url will look like: `http(s)://[mydomain]/user/saml/[Authentication Name]/acs`.
- The service provider metadata url will look like: `http(s)://[mydomain]/user/saml/[Authentication Name]/metadata`.
-5
View File
@@ -91,8 +91,6 @@ require (
github.com/quasoft/websspi v1.1.2 github.com/quasoft/websspi v1.1.2
github.com/redis/go-redis/v9 v9.4.0 github.com/redis/go-redis/v9 v9.4.0
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/russellhaering/gosaml2 v0.9.1
github.com/russellhaering/goxmldsig v1.3.0
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 github.com/santhosh-tekuri/jsonschema/v5 v5.3.1
github.com/sassoftware/go-rpmutils v0.2.1-0.20240124161140-277b154961dd github.com/sassoftware/go-rpmutils v0.2.1-0.20240124161140-277b154961dd
github.com/sergi/go-diff v1.3.1 github.com/sergi/go-diff v1.3.1
@@ -145,7 +143,6 @@ require (
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/aymerick/douceur v0.2.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect
github.com/beevik/etree v1.1.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/bits-and-blooms/bitset v1.13.0 // indirect github.com/bits-and-blooms/bitset v1.13.0 // indirect
github.com/blevesearch/bleve_index_api v1.1.5 // indirect github.com/blevesearch/bleve_index_api v1.1.5 // indirect
@@ -219,7 +216,6 @@ require (
github.com/imdario/mergo v0.3.16 // indirect github.com/imdario/mergo v0.3.16 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jessevdk/go-flags v1.5.0 // indirect github.com/jessevdk/go-flags v1.5.0 // indirect
github.com/jonboulle/clockwork v0.3.0 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect github.com/klauspost/pgzip v1.2.6 // indirect
@@ -229,7 +225,6 @@ require (
github.com/magiconair/properties v1.8.7 // indirect github.com/magiconair/properties v1.8.7 // indirect
github.com/mailru/easyjson v0.7.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect
github.com/markbates/going v1.0.3 // indirect github.com/markbates/going v1.0.3 // indirect
github.com/mattermost/xml-roundtrip-validator v0.1.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mholt/acmez v1.2.0 // indirect github.com/mholt/acmez v1.2.0 // indirect
-12
View File
@@ -130,8 +130,6 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3d
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bits-and-blooms/bitset v1.1.10/go.mod h1:w0XsmFg8qg6cmpTtJ0z3pKgjTDBMMnI/+I2syrE6XBE= github.com/bits-and-blooms/bitset v1.1.10/go.mod h1:w0XsmFg8qg6cmpTtJ0z3pKgjTDBMMnI/+I2syrE6XBE=
@@ -568,9 +566,6 @@ github.com/jhillyerd/enmime v1.1.0 h1:ubaIzg68VY7CMCe2YbHe6nkRvU9vujixTkNz3EBvZO
github.com/jhillyerd/enmime v1.1.0/go.mod h1:FRFuUPCLh8PByQv+8xRcLO9QHqaqTqreYhopv5eyk4I= github.com/jhillyerd/enmime v1.1.0/go.mod h1:FRFuUPCLh8PByQv+8xRcLO9QHqaqTqreYhopv5eyk4I=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
github.com/jonboulle/clockwork v0.3.0 h1:9BSCMi8C+0qdApAp4auwX0RkLGUjs956h0EkuQymUhg=
github.com/jonboulle/clockwork v0.3.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
@@ -639,8 +634,6 @@ github.com/markbates/going v1.0.3 h1:mY45T5TvW+Xz5A6jY7lf4+NLg9D8+iuStIHyR7M8qsE
github.com/markbates/going v1.0.3/go.mod h1:fQiT6v6yQar9UD6bd/D4Z5Afbk9J6BBVBtLiyY4gp2o= github.com/markbates/going v1.0.3/go.mod h1:fQiT6v6yQar9UD6bd/D4Z5Afbk9J6BBVBtLiyY4gp2o=
github.com/markbates/goth v1.78.0 h1:7VEIFDycJp9deyVv3YraGBPdD0ZYQW93Y3Aw1eVP3BY= github.com/markbates/goth v1.78.0 h1:7VEIFDycJp9deyVv3YraGBPdD0ZYQW93Y3Aw1eVP3BY=
github.com/markbates/goth v1.78.0/go.mod h1:X6xdNgpapSENS0O35iTBBcMHoJDQDfI9bJl+APCkYMc= github.com/markbates/goth v1.78.0/go.mod h1:X6xdNgpapSENS0O35iTBBcMHoJDQDfI9bJl+APCkYMc=
github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU=
github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
@@ -773,17 +766,12 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/russellhaering/gosaml2 v0.9.1 h1:H/whrl8NuSoxyW46Ww5lKPskm+5K+qYLw9afqJ/Zef0=
github.com/russellhaering/gosaml2 v0.9.1/go.mod h1:ja+qgbayxm+0mxBRLMSUuX3COqy+sb0RRhIGun/W2kc=
github.com/russellhaering/goxmldsig v1.3.0 h1:DllIWUgMy0cRUMfGiASiYEa35nsieyD3cigIwLonTPM=
github.com/russellhaering/goxmldsig v1.3.0/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+15 -5
View File
@@ -8,7 +8,6 @@ import (
"crypto/sha256" "crypto/sha256"
"encoding/base32" "encoding/base32"
"encoding/base64" "encoding/base64"
"encoding/gob"
"fmt" "fmt"
"net" "net"
"net/url" "net/url"
@@ -82,10 +81,6 @@ func Init(ctx context.Context) error {
builtinAllClientIDs = append(builtinAllClientIDs, clientID) builtinAllClientIDs = append(builtinAllClientIDs, clientID)
} }
// This is needed in order to encode and store the struct in the goth/gothic session
// during the process of linking the external user.
gob.Register(LinkAccountUser{})
var registeredApps []*OAuth2Application var registeredApps []*OAuth2Application
if err := db.GetEngine(ctx).In("client_id", builtinAllClientIDs).Find(&registeredApps); err != nil { if err := db.GetEngine(ctx).In("client_id", builtinAllClientIDs).Find(&registeredApps); err != nil {
return err return err
@@ -610,6 +605,21 @@ func (err ErrOAuthApplicationNotFound) Unwrap() error {
return util.ErrNotExist return util.ErrNotExist
} }
// GetActiveOAuth2SourceByName returns a OAuth2 AuthSource based on the given name
func GetActiveOAuth2SourceByName(ctx context.Context, name string) (*Source, error) {
authSource := new(Source)
has, err := db.GetEngine(ctx).Where("name = ? and type = ? and is_active = ?", name, OAuth2, true).Get(authSource)
if err != nil {
return nil, err
}
if !has {
return nil, fmt.Errorf("oauth2 source not found, name: %q", name)
}
return authSource, nil
}
func DeleteOAuth2RelictsByUserID(ctx context.Context, userID int64) error { func DeleteOAuth2RelictsByUserID(ctx context.Context, userID int64) error {
deleteCond := builder.Select("id").From("oauth2_grant").Where(builder.Eq{"oauth2_grant.user_id": userID}) deleteCond := builder.Select("id").From("oauth2_grant").Where(builder.Eq{"oauth2_grant.user_id": userID})
-38
View File
@@ -14,7 +14,6 @@ import (
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"github.com/markbates/goth"
"xorm.io/builder" "xorm.io/builder"
"xorm.io/xorm" "xorm.io/xorm"
"xorm.io/xorm/convert" "xorm.io/xorm/convert"
@@ -33,7 +32,6 @@ const (
DLDAP // 5 DLDAP // 5
OAuth2 // 6 OAuth2 // 6
SSPI // 7 SSPI // 7
SAML // 8
) )
// String returns the string name of the LoginType // String returns the string name of the LoginType
@@ -54,7 +52,6 @@ var Names = map[Type]string{
PAM: "PAM", PAM: "PAM",
OAuth2: "OAuth2", OAuth2: "OAuth2",
SSPI: "SPNEGO with SSPI", SSPI: "SPNEGO with SSPI",
SAML: "SAML",
} }
// Config represents login config as far as the db is concerned // Config represents login config as far as the db is concerned
@@ -124,12 +121,6 @@ type Source struct {
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
} }
// LinkAccountUser is used to link an external user with a local user
type LinkAccountUser struct {
Type Type
GothUser goth.User
}
// TableName xorm will read the table name from this method // TableName xorm will read the table name from this method
func (Source) TableName() string { func (Source) TableName() string {
return "login_source" return "login_source"
@@ -189,11 +180,6 @@ func (source *Source) IsSSPI() bool {
return source.Type == SSPI return source.Type == SSPI
} }
// IsSAML returns true of this source is of the SAML type.
func (source *Source) IsSAML() bool {
return source.Type == SAML
}
// HasTLS returns true of this source supports TLS. // HasTLS returns true of this source supports TLS.
func (source *Source) HasTLS() bool { func (source *Source) HasTLS() bool {
hasTLSer, ok := source.Cfg.(HasTLSer) hasTLSer, ok := source.Cfg.(HasTLSer)
@@ -406,27 +392,3 @@ func IsErrSourceInUse(err error) bool {
func (err ErrSourceInUse) Error() string { func (err ErrSourceInUse) Error() string {
return fmt.Sprintf("login source is still used by some users [id: %d]", err.ID) return fmt.Sprintf("login source is still used by some users [id: %d]", err.ID)
} }
// GetActiveAuthProviderSources returns all activated sources
func GetActiveAuthProviderSources(ctx context.Context, authType Type) ([]*Source, error) {
sources := make([]*Source, 0, 1)
if err := db.GetEngine(ctx).Where("is_active = ? and type = ?", true, authType).Find(&sources); err != nil {
return nil, err
}
return sources, nil
}
// GetActiveAuthSourceByName returns an AuthSource based on the given name and type
func GetActiveAuthSourceByName(ctx context.Context, name string, authType Type) (*Source, error) {
authSource := new(Source)
has, err := db.GetEngine(ctx).Where("name = ? and type = ? and is_active = ?", name, authType, true).Get(authSource)
if err != nil {
return nil, err
}
if !has {
return nil, fmt.Errorf("auth source not found, name: %q", name)
}
return authSource, nil
}
-14
View File
@@ -523,9 +523,6 @@ Content = Content
SSPISeparatorReplacement = Separator SSPISeparatorReplacement = Separator
SSPIDefaultLanguage = Default Language SSPIDefaultLanguage = Default Language
SAMLMetadata = Either SAML Identity Provider metadata URL or XML
SAMLMetadataURL = SAML Identity Provider metadata URL is invalid
require_error = ` cannot be empty.` require_error = ` cannot be empty.`
alpha_dash_error = ` should contain only alphanumeric, dash ('-') and underscore ('_') characters.` alpha_dash_error = ` should contain only alphanumeric, dash ('-') and underscore ('_') characters.`
alpha_dash_dot_error = ` should contain only alphanumeric, dash ('-'), underscore ('_') and dot ('.') characters.` alpha_dash_dot_error = ` should contain only alphanumeric, dash ('-'), underscore ('_') and dot ('.') characters.`
@@ -3032,18 +3029,7 @@ auths.sspi_separator_replacement = Separator to use instead of \, / and @
auths.sspi_separator_replacement_helper = The character to use to replace the separators of down-level logon names (eg. the \ in "DOMAIN\user") and user principal names (eg. the @ in "user@example.org"). auths.sspi_separator_replacement_helper = The character to use to replace the separators of down-level logon names (eg. the \ in "DOMAIN\user") and user principal names (eg. the @ in "user@example.org").
auths.sspi_default_language = Default user language auths.sspi_default_language = Default user language
auths.sspi_default_language_helper = Default language for users automatically created by SSPI auth method. Leave empty if you prefer language to be automatically detected. auths.sspi_default_language_helper = Default language for users automatically created by SSPI auth method. Leave empty if you prefer language to be automatically detected.
auths.saml_nameidformat = SAML NameID Format
auths.saml_identity_provider_metadata_url = Identity Provider Metadata URL
auths.saml_identity_provider_metadata = Identity Provider Metadata XML
auths.saml_insecure_skip_assertion_signature_validation = [Insecure] Skip Assertion Signature Validation
auths.saml_service_provider_certificate = Service Provider Certificate
auths.saml_service_provider_private_key = Service Provider Private Key
auths.saml_identity_provider_email_assertion_key = Email Assertion Key
auths.saml_identity_provider_name_assertion_key = Name Assertion Key
auths.saml_identity_provider_username_assertion_key = Username Assertion Key
auths.saml_icon_url = Icon URL
auths.tips = Tips auths.tips = Tips
auths.tips.saml = Documentation can be found at https://docs.gitea.com/usage/authentication#saml
auths.tips.oauth2.general = OAuth2 Authentication auths.tips.oauth2.general = OAuth2 Authentication
auths.tips.oauth2.general.tip = When registering a new OAuth2 authentication, the callback/redirect URL should be: auths.tips.oauth2.general.tip = When registering a new OAuth2 authentication, the callback/redirect URL should be:
auths.tip.oauth2_provider = OAuth2 Provider auths.tip.oauth2_provider = OAuth2 Provider
-2
View File
@@ -35,7 +35,6 @@ import (
actions_service "code.gitea.io/gitea/services/actions" actions_service "code.gitea.io/gitea/services/actions"
"code.gitea.io/gitea/services/auth" "code.gitea.io/gitea/services/auth"
"code.gitea.io/gitea/services/auth/source/oauth2" "code.gitea.io/gitea/services/auth/source/oauth2"
"code.gitea.io/gitea/services/auth/source/saml"
"code.gitea.io/gitea/services/automerge" "code.gitea.io/gitea/services/automerge"
"code.gitea.io/gitea/services/cron" "code.gitea.io/gitea/services/cron"
feed_service "code.gitea.io/gitea/services/feed" feed_service "code.gitea.io/gitea/services/feed"
@@ -139,7 +138,6 @@ func InitWebInstalled(ctx context.Context) {
log.Info("ORM engine initialization successful!") log.Info("ORM engine initialization successful!")
mustInit(system.Init) mustInit(system.Init)
mustInitCtx(ctx, oauth2.Init) mustInitCtx(ctx, oauth2.Init)
mustInitCtx(ctx, saml.Init)
mustInit(release_service.Init) mustInit(release_service.Init)
-84
View File
@@ -1,12 +1,9 @@
// Copyright 2014 The Gogs Authors. All rights reserved. // Copyright 2014 The Gogs Authors. All rights reserved.
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
package admin package admin
import ( import (
"crypto/tls"
"crypto/x509"
"errors" "errors"
"fmt" "fmt"
"net/http" "net/http"
@@ -28,7 +25,6 @@ import (
"code.gitea.io/gitea/services/auth/source/ldap" "code.gitea.io/gitea/services/auth/source/ldap"
"code.gitea.io/gitea/services/auth/source/oauth2" "code.gitea.io/gitea/services/auth/source/oauth2"
pam_service "code.gitea.io/gitea/services/auth/source/pam" pam_service "code.gitea.io/gitea/services/auth/source/pam"
"code.gitea.io/gitea/services/auth/source/saml"
"code.gitea.io/gitea/services/auth/source/smtp" "code.gitea.io/gitea/services/auth/source/smtp"
"code.gitea.io/gitea/services/auth/source/sspi" "code.gitea.io/gitea/services/auth/source/sspi"
"code.gitea.io/gitea/services/forms" "code.gitea.io/gitea/services/forms"
@@ -75,7 +71,6 @@ var (
{auth.SMTP.String(), auth.SMTP}, {auth.SMTP.String(), auth.SMTP},
{auth.OAuth2.String(), auth.OAuth2}, {auth.OAuth2.String(), auth.OAuth2},
{auth.SSPI.String(), auth.SSPI}, {auth.SSPI.String(), auth.SSPI},
{auth.SAML.String(), auth.SAML},
} }
if pam.Supported { if pam.Supported {
items = append(items, dropdownItem{auth.Names[auth.PAM], auth.PAM}) items = append(items, dropdownItem{auth.Names[auth.PAM], auth.PAM})
@@ -88,16 +83,6 @@ var (
{ldap.SecurityProtocolNames[ldap.SecurityProtocolLDAPS], ldap.SecurityProtocolLDAPS}, {ldap.SecurityProtocolNames[ldap.SecurityProtocolLDAPS], ldap.SecurityProtocolLDAPS},
{ldap.SecurityProtocolNames[ldap.SecurityProtocolStartTLS], ldap.SecurityProtocolStartTLS}, {ldap.SecurityProtocolNames[ldap.SecurityProtocolStartTLS], ldap.SecurityProtocolStartTLS},
} }
nameIDFormats = []dropdownItem{
{saml.NameIDFormatNames[saml.SAML20Persistent], saml.SAML20Persistent}, // use this as default value
{saml.NameIDFormatNames[saml.SAML11Email], saml.SAML11Email},
{saml.NameIDFormatNames[saml.SAML11Persistent], saml.SAML11Persistent},
{saml.NameIDFormatNames[saml.SAML11Unspecified], saml.SAML11Unspecified},
{saml.NameIDFormatNames[saml.SAML20Email], saml.SAML20Email},
{saml.NameIDFormatNames[saml.SAML20Transient], saml.SAML20Transient},
{saml.NameIDFormatNames[saml.SAML20Unspecified], saml.SAML20Unspecified},
}
) )
// NewAuthSource render adding a new auth source page // NewAuthSource render adding a new auth source page
@@ -113,8 +98,6 @@ func NewAuthSource(ctx *context.Context) {
ctx.Data["is_sync_enabled"] = true ctx.Data["is_sync_enabled"] = true
ctx.Data["AuthSources"] = authSources ctx.Data["AuthSources"] = authSources
ctx.Data["SecurityProtocols"] = securityProtocols ctx.Data["SecurityProtocols"] = securityProtocols
ctx.Data["CurrentNameIDFormat"] = saml.NameIDFormatNames[saml.SAML20Persistent]
ctx.Data["NameIDFormats"] = nameIDFormats
ctx.Data["SMTPAuths"] = smtp.Authenticators ctx.Data["SMTPAuths"] = smtp.Authenticators
oauth2providers := oauth2.GetSupportedOAuth2Providers() oauth2providers := oauth2.GetSupportedOAuth2Providers()
ctx.Data["OAuth2Providers"] = oauth2providers ctx.Data["OAuth2Providers"] = oauth2providers
@@ -248,52 +231,6 @@ func parseSSPIConfig(ctx *context.Context, form forms.AuthenticationForm) (*sspi
}, nil }, nil
} }
func parseSAMLConfig(ctx *context.Context, form forms.AuthenticationForm) (*saml.Source, error) {
if util.IsEmptyString(form.IdentityProviderMetadata) && util.IsEmptyString(form.IdentityProviderMetadataURL) {
return nil, fmt.Errorf("%s %s", ctx.Tr("form.SAMLMetadata"), ctx.Tr("form.require_error"))
}
if !util.IsEmptyString(form.IdentityProviderMetadataURL) {
_, err := url.Parse(form.IdentityProviderMetadataURL)
if err != nil {
return nil, fmt.Errorf("%s", ctx.Tr("form.SAMLMetadataURL"))
}
}
// check the integrity of the certificate and private key (autogenerated if these form fields are blank)
if !util.IsEmptyString(form.ServiceProviderCertificate) && !util.IsEmptyString(form.ServiceProviderPrivateKey) {
keyPair, err := tls.X509KeyPair([]byte(form.ServiceProviderCertificate), []byte(form.ServiceProviderPrivateKey))
if err != nil {
return nil, err
}
keyPair.Leaf, err = x509.ParseCertificate(keyPair.Certificate[0])
if err != nil {
return nil, err
}
} else {
privateKey, cert, err := saml.GenerateSAMLSPKeypair()
if err != nil {
return nil, err
}
form.ServiceProviderPrivateKey = privateKey
form.ServiceProviderCertificate = cert
}
return &saml.Source{
IdentityProviderMetadata: form.IdentityProviderMetadata,
IdentityProviderMetadataURL: form.IdentityProviderMetadataURL,
InsecureSkipAssertionSignatureValidation: form.InsecureSkipAssertionSignatureValidation,
NameIDFormat: saml.NameIDFormat(form.NameIDFormat),
ServiceProviderCertificate: form.ServiceProviderCertificate,
ServiceProviderPrivateKey: form.ServiceProviderPrivateKey,
EmailAssertionKey: form.EmailAssertionKey,
NameAssertionKey: form.NameAssertionKey,
UsernameAssertionKey: form.UsernameAssertionKey,
IconURL: form.SAMLIconURL,
}, nil
}
// NewAuthSourcePost response for adding an auth source // NewAuthSourcePost response for adding an auth source
func NewAuthSourcePost(ctx *context.Context) { func NewAuthSourcePost(ctx *context.Context) {
form := *web.GetForm(ctx).(*forms.AuthenticationForm) form := *web.GetForm(ctx).(*forms.AuthenticationForm)
@@ -307,8 +244,6 @@ func NewAuthSourcePost(ctx *context.Context) {
ctx.Data["SMTPAuths"] = smtp.Authenticators ctx.Data["SMTPAuths"] = smtp.Authenticators
oauth2providers := oauth2.GetSupportedOAuth2Providers() oauth2providers := oauth2.GetSupportedOAuth2Providers()
ctx.Data["OAuth2Providers"] = oauth2providers ctx.Data["OAuth2Providers"] = oauth2providers
ctx.Data["CurrentNameIDFormat"] = saml.NameIDFormatNames[saml.NameIDFormat(form.NameIDFormat)]
ctx.Data["NameIDFormats"] = nameIDFormats
ctx.Data["SSPIAutoCreateUsers"] = true ctx.Data["SSPIAutoCreateUsers"] = true
ctx.Data["SSPIAutoActivateUsers"] = true ctx.Data["SSPIAutoActivateUsers"] = true
@@ -355,13 +290,6 @@ func NewAuthSourcePost(ctx *context.Context) {
ctx.RenderWithErr(ctx.Tr("admin.auths.login_source_of_type_exist"), tplAuthNew, form) ctx.RenderWithErr(ctx.Tr("admin.auths.login_source_of_type_exist"), tplAuthNew, form)
return return
} }
case auth.SAML:
var err error
config, err = parseSAMLConfig(ctx, form)
if err != nil {
ctx.RenderWithErr(err.Error(), tplAuthNew, form)
return
}
default: default:
ctx.Error(http.StatusBadRequest) ctx.Error(http.StatusBadRequest)
return return
@@ -408,7 +336,6 @@ func EditAuthSource(ctx *context.Context) {
ctx.Data["SMTPAuths"] = smtp.Authenticators ctx.Data["SMTPAuths"] = smtp.Authenticators
oauth2providers := oauth2.GetSupportedOAuth2Providers() oauth2providers := oauth2.GetSupportedOAuth2Providers()
ctx.Data["OAuth2Providers"] = oauth2providers ctx.Data["OAuth2Providers"] = oauth2providers
ctx.Data["NameIDFormats"] = nameIDFormats
source, err := auth.GetSourceByID(ctx, ctx.ParamsInt64(":authid")) source, err := auth.GetSourceByID(ctx, ctx.ParamsInt64(":authid"))
if err != nil { if err != nil {
@@ -417,9 +344,6 @@ func EditAuthSource(ctx *context.Context) {
} }
ctx.Data["Source"] = source ctx.Data["Source"] = source
ctx.Data["HasTLS"] = source.HasTLS() ctx.Data["HasTLS"] = source.HasTLS()
if source.IsSAML() {
ctx.Data["CurrentNameIDFormat"] = saml.NameIDFormatNames[source.Cfg.(*saml.Source).NameIDFormat]
}
if source.IsOAuth2() { if source.IsOAuth2() {
type Named interface { type Named interface {
@@ -454,8 +378,6 @@ func EditAuthSourcePost(ctx *context.Context) {
} }
ctx.Data["Source"] = source ctx.Data["Source"] = source
ctx.Data["HasTLS"] = source.HasTLS() ctx.Data["HasTLS"] = source.HasTLS()
ctx.Data["CurrentNameIDFormat"] = saml.NameIDFormatNames[saml.SAML20Persistent]
ctx.Data["NameIDFormats"] = nameIDFormats
if ctx.HasError() { if ctx.HasError() {
ctx.HTML(http.StatusOK, tplAuthEdit) ctx.HTML(http.StatusOK, tplAuthEdit)
@@ -490,12 +412,6 @@ func EditAuthSourcePost(ctx *context.Context) {
ctx.RenderWithErr(err.Error(), tplAuthEdit, form) ctx.RenderWithErr(err.Error(), tplAuthEdit, form)
return return
} }
case auth.SAML:
config, err = parseSAMLConfig(ctx, form)
if err != nil {
ctx.RenderWithErr(err.Error(), tplAuthEdit, form)
return
}
default: default:
ctx.Error(http.StatusBadRequest) ctx.Error(http.StatusBadRequest)
return return
+9 -26
View File
@@ -28,7 +28,6 @@ import (
"code.gitea.io/gitea/routers/utils" "code.gitea.io/gitea/routers/utils"
auth_service "code.gitea.io/gitea/services/auth" auth_service "code.gitea.io/gitea/services/auth"
"code.gitea.io/gitea/services/auth/source/oauth2" "code.gitea.io/gitea/services/auth/source/oauth2"
"code.gitea.io/gitea/services/auth/source/saml"
"code.gitea.io/gitea/services/externalaccount" "code.gitea.io/gitea/services/externalaccount"
"code.gitea.io/gitea/services/forms" "code.gitea.io/gitea/services/forms"
"code.gitea.io/gitea/services/mailer" "code.gitea.io/gitea/services/mailer"
@@ -171,14 +170,6 @@ func SignIn(ctx *context.Context) {
return return
} }
ctx.Data["OAuth2Providers"] = oauth2Providers ctx.Data["OAuth2Providers"] = oauth2Providers
samlProviders, err := saml.GetSAMLProviders(ctx, util.OptionalBoolTrue)
if err != nil {
ctx.ServerError("UserSignIn", err)
return
}
ctx.Data["SAMLProviders"] = samlProviders
ctx.Data["Title"] = ctx.Tr("sign_in") ctx.Data["Title"] = ctx.Tr("sign_in")
ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login" ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login"
ctx.Data["PageIsSignIn"] = true ctx.Data["PageIsSignIn"] = true
@@ -202,14 +193,6 @@ func SignInPost(ctx *context.Context) {
return return
} }
ctx.Data["OAuth2Providers"] = oauth2Providers ctx.Data["OAuth2Providers"] = oauth2Providers
samlProviders, err := saml.GetSAMLProviders(ctx, util.OptionalBoolTrue)
if err != nil {
ctx.ServerError("UserSignIn", err)
return
}
ctx.Data["SAMLProviders"] = samlProviders
ctx.Data["Title"] = ctx.Tr("sign_in") ctx.Data["Title"] = ctx.Tr("sign_in")
ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login" ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login"
ctx.Data["PageIsSignIn"] = true ctx.Data["PageIsSignIn"] = true
@@ -521,7 +504,7 @@ func SignUpPost(ctx *context.Context) {
Passwd: form.Password, Passwd: form.Password,
} }
if !createAndHandleCreatedUser(ctx, tplSignUp, form, u, nil, nil, false, auth.NoType) { if !createAndHandleCreatedUser(ctx, tplSignUp, form, u, nil, nil, false) {
// error already handled // error already handled
return return
} }
@@ -532,16 +515,16 @@ func SignUpPost(ctx *context.Context) {
// createAndHandleCreatedUser calls createUserInContext and // createAndHandleCreatedUser calls createUserInContext and
// then handleUserCreated. // then handleUserCreated.
func createAndHandleCreatedUser(ctx *context.Context, tpl base.TplName, form any, u *user_model.User, overwrites *user_model.CreateUserOverwriteOptions, gothUser *goth.User, allowLink bool, authType auth.Type) bool { func createAndHandleCreatedUser(ctx *context.Context, tpl base.TplName, form any, u *user_model.User, overwrites *user_model.CreateUserOverwriteOptions, gothUser *goth.User, allowLink bool) bool {
if !createUserInContext(ctx, tpl, form, u, overwrites, gothUser, allowLink, authType) { if !createUserInContext(ctx, tpl, form, u, overwrites, gothUser, allowLink) {
return false return false
} }
return handleUserCreated(ctx, u, gothUser, authType) return handleUserCreated(ctx, u, gothUser)
} }
// createUserInContext creates a user and handles errors within a given context. // createUserInContext creates a user and handles errors within a given context.
// Optionally a template can be specified. // Optionally a template can be specified.
func createUserInContext(ctx *context.Context, tpl base.TplName, form any, u *user_model.User, overwrites *user_model.CreateUserOverwriteOptions, gothUser *goth.User, allowLink bool, authType auth.Type) (ok bool) { func createUserInContext(ctx *context.Context, tpl base.TplName, form any, u *user_model.User, overwrites *user_model.CreateUserOverwriteOptions, gothUser *goth.User, allowLink bool) (ok bool) {
if err := user_model.CreateUser(ctx, u, overwrites); err != nil { if err := user_model.CreateUser(ctx, u, overwrites); err != nil {
if allowLink && (user_model.IsErrUserAlreadyExist(err) || user_model.IsErrEmailAlreadyUsed(err)) { if allowLink && (user_model.IsErrUserAlreadyExist(err) || user_model.IsErrEmailAlreadyUsed(err)) {
if setting.OAuth2Client.AccountLinking == setting.OAuth2AccountLinkingAuto { if setting.OAuth2Client.AccountLinking == setting.OAuth2AccountLinkingAuto {
@@ -558,10 +541,10 @@ func createUserInContext(ctx *context.Context, tpl base.TplName, form any, u *us
} }
// TODO: probably we should respect 'remember' user's choice... // TODO: probably we should respect 'remember' user's choice...
linkAccount(ctx, user, *gothUser, true, authType) linkAccount(ctx, user, *gothUser, true)
return false // user is already created here, all redirects are handled return false // user is already created here, all redirects are handled
} else if setting.OAuth2Client.AccountLinking == setting.OAuth2AccountLinkingLogin { } else if setting.OAuth2Client.AccountLinking == setting.OAuth2AccountLinkingLogin {
showLinkingLogin(ctx, *gothUser, authType) showLinkingLogin(ctx, *gothUser)
return false // user will be created only after linking login return false // user will be created only after linking login
} }
} }
@@ -607,7 +590,7 @@ func createUserInContext(ctx *context.Context, tpl base.TplName, form any, u *us
// handleUserCreated does additional steps after a new user is created. // handleUserCreated does additional steps after a new user is created.
// It auto-sets admin for the only user, updates the optional external user and // It auto-sets admin for the only user, updates the optional external user and
// sends a confirmation email if required. // sends a confirmation email if required.
func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth.User, authType auth.Type) (ok bool) { func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth.User) (ok bool) {
// Auto-set admin for the only user. // Auto-set admin for the only user.
if user_model.CountUsers(ctx, nil) == 1 { if user_model.CountUsers(ctx, nil) == 1 {
opts := &user_service.UpdateOptions{ opts := &user_service.UpdateOptions{
@@ -623,7 +606,7 @@ func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth.
// update external user information // update external user information
if gothUser != nil { if gothUser != nil {
if err := externalaccount.UpdateExternalUser(ctx, u, *gothUser, authType); err != nil { if err := externalaccount.UpdateExternalUser(ctx, u, *gothUser); err != nil {
if !errors.Is(err, util.ErrNotExist) { if !errors.Is(err, util.ErrNotExist) {
log.Error("UpdateExternalUser failed: %v", err) log.Error("UpdateExternalUser failed: %v", err)
} }
+20 -25
View File
@@ -48,13 +48,13 @@ func LinkAccount(ctx *context.Context) {
ctx.Data["SignInLink"] = setting.AppSubURL + "/user/link_account_signin" ctx.Data["SignInLink"] = setting.AppSubURL + "/user/link_account_signin"
ctx.Data["SignUpLink"] = setting.AppSubURL + "/user/link_account_signup" ctx.Data["SignUpLink"] = setting.AppSubURL + "/user/link_account_signup"
externalLinkUser := ctx.Session.Get("linkAccountUser") gothUser := ctx.Session.Get("linkAccountGothUser")
if externalLinkUser == nil { if gothUser == nil {
ctx.ServerError("UserSignIn", errors.New("not in LinkAccount session")) ctx.ServerError("UserSignIn", errors.New("not in LinkAccount session"))
return return
} }
gu := externalLinkUser.(auth.LinkAccountUser).GothUser gu, _ := gothUser.(goth.User)
uname, err := getUserName(&gu) uname, err := getUserName(&gu)
if err != nil { if err != nil {
ctx.ServerError("UserSignIn", err) ctx.ServerError("UserSignIn", err)
@@ -135,14 +135,12 @@ func LinkAccountPostSignIn(ctx *context.Context) {
ctx.Data["SignInLink"] = setting.AppSubURL + "/user/link_account_signin" ctx.Data["SignInLink"] = setting.AppSubURL + "/user/link_account_signin"
ctx.Data["SignUpLink"] = setting.AppSubURL + "/user/link_account_signup" ctx.Data["SignUpLink"] = setting.AppSubURL + "/user/link_account_signup"
externalLinkUserInterface := ctx.Session.Get("linkAccountUser") gothUser := ctx.Session.Get("linkAccountGothUser")
if externalLinkUserInterface == nil { if gothUser == nil {
ctx.ServerError("UserSignIn", errors.New("not in LinkAccount session")) ctx.ServerError("UserSignIn", errors.New("not in LinkAccount session"))
return return
} }
externalLinkUser := externalLinkUserInterface.(auth.LinkAccountUser)
if ctx.HasError() { if ctx.HasError() {
ctx.HTML(http.StatusOK, tplLinkAccount) ctx.HTML(http.StatusOK, tplLinkAccount)
return return
@@ -154,10 +152,10 @@ func LinkAccountPostSignIn(ctx *context.Context) {
return return
} }
linkAccount(ctx, u, externalLinkUser.GothUser, signInForm.Remember, externalLinkUser.Type) linkAccount(ctx, u, gothUser.(goth.User), signInForm.Remember)
} }
func linkAccount(ctx *context.Context, u *user_model.User, gothUser goth.User, remember bool, authType auth.Type) { func linkAccount(ctx *context.Context, u *user_model.User, gothUser goth.User, remember bool) {
updateAvatarIfNeed(ctx, gothUser.AvatarURL, u) updateAvatarIfNeed(ctx, gothUser.AvatarURL, u)
// If this user is enrolled in 2FA, we can't sign the user in just yet. // If this user is enrolled in 2FA, we can't sign the user in just yet.
@@ -170,7 +168,7 @@ func linkAccount(ctx *context.Context, u *user_model.User, gothUser goth.User, r
return return
} }
err = externalaccount.LinkAccountToUser(ctx, u, gothUser, authType) err = externalaccount.LinkAccountToUser(ctx, u, gothUser)
if err != nil { if err != nil {
ctx.ServerError("UserLinkAccount", err) ctx.ServerError("UserLinkAccount", err)
return return
@@ -224,14 +222,14 @@ func LinkAccountPostRegister(ctx *context.Context) {
ctx.Data["SignInLink"] = setting.AppSubURL + "/user/link_account_signin" ctx.Data["SignInLink"] = setting.AppSubURL + "/user/link_account_signin"
ctx.Data["SignUpLink"] = setting.AppSubURL + "/user/link_account_signup" ctx.Data["SignUpLink"] = setting.AppSubURL + "/user/link_account_signup"
externalLinkUser := ctx.Session.Get("linkAccountUser") gothUserInterface := ctx.Session.Get("linkAccountGothUser")
if externalLinkUser == nil { if gothUserInterface == nil {
ctx.ServerError("UserSignUp", errors.New("not in LinkAccount session")) ctx.ServerError("UserSignUp", errors.New("not in LinkAccount session"))
return return
} }
linkUser, ok := externalLinkUser.(auth.LinkAccountUser) gothUser, ok := gothUserInterface.(goth.User)
if !ok { if !ok {
ctx.ServerError("UserSignUp", fmt.Errorf("session linkAccountUser type is %t but not goth.User", externalLinkUser)) ctx.ServerError("UserSignUp", fmt.Errorf("session linkAccountGothUser type is %t but not goth.User", gothUserInterface))
return return
} }
@@ -277,7 +275,7 @@ func LinkAccountPostRegister(ctx *context.Context) {
} }
} }
authSource, err := auth.GetActiveAuthSourceByName(ctx, linkUser.GothUser.Provider, linkUser.Type) authSource, err := auth.GetActiveOAuth2SourceByName(ctx, gothUser.Provider)
if err != nil { if err != nil {
ctx.ServerError("CreateUser", err) ctx.ServerError("CreateUser", err)
return return
@@ -287,24 +285,21 @@ func LinkAccountPostRegister(ctx *context.Context) {
Name: form.UserName, Name: form.UserName,
Email: form.Email, Email: form.Email,
Passwd: form.Password, Passwd: form.Password,
LoginType: authSource.Type, LoginType: auth.OAuth2,
LoginSource: authSource.ID, LoginSource: authSource.ID,
LoginName: linkUser.GothUser.UserID, LoginName: gothUser.UserID,
} }
if !createAndHandleCreatedUser(ctx, tplLinkAccount, form, u, nil, &linkUser.GothUser, false, linkUser.Type) { if !createAndHandleCreatedUser(ctx, tplLinkAccount, form, u, nil, &gothUser, false) {
// error already handled // error already handled
return return
} }
if linkUser.Type == auth.OAuth2 { source := authSource.Cfg.(*oauth2.Source)
source := authSource.Cfg.(*oauth2.Source) if err := syncGroupsToTeams(ctx, source, &gothUser, u); err != nil {
if err := syncGroupsToTeams(ctx, source, &linkUser.GothUser, u); err != nil { ctx.ServerError("SyncGroupsToTeams", err)
ctx.ServerError("SyncGroupsToTeams", err) return
return
}
} }
// TODO we will support some form of group mapping for SAML
handleSignIn(ctx, u, false) handleSignIn(ctx, u, false)
} }
+8 -11
View File
@@ -841,7 +841,7 @@ func handleAuthorizeError(ctx *context.Context, authErr AuthorizeError, redirect
func SignInOAuth(ctx *context.Context) { func SignInOAuth(ctx *context.Context) {
provider := ctx.Params(":provider") provider := ctx.Params(":provider")
authSource, err := auth.GetActiveAuthSourceByName(ctx, provider, auth.OAuth2) authSource, err := auth.GetActiveOAuth2SourceByName(ctx, provider)
if err != nil { if err != nil {
ctx.ServerError("SignIn", err) ctx.ServerError("SignIn", err)
return return
@@ -892,7 +892,7 @@ func SignInOAuthCallback(ctx *context.Context) {
} }
// first look if the provider is still active // first look if the provider is still active
authSource, err := auth.GetActiveAuthSourceByName(ctx, provider, auth.OAuth2) authSource, err := auth.GetActiveOAuth2SourceByName(ctx, provider)
if err != nil { if err != nil {
ctx.ServerError("SignIn", err) ctx.ServerError("SignIn", err)
return return
@@ -935,7 +935,7 @@ func SignInOAuthCallback(ctx *context.Context) {
if u == nil { if u == nil {
if ctx.Doer != nil { if ctx.Doer != nil {
// attach user to already logged in user // attach user to already logged in user
err = externalaccount.LinkAccountToUser(ctx, ctx.Doer, gothUser, auth.OAuth2) err = externalaccount.LinkAccountToUser(ctx, ctx.Doer, gothUser)
if err != nil { if err != nil {
ctx.ServerError("UserLinkAccount", err) ctx.ServerError("UserLinkAccount", err)
return return
@@ -988,7 +988,7 @@ func SignInOAuthCallback(ctx *context.Context) {
u.IsAdmin = isAdmin.ValueOrDefault(false) u.IsAdmin = isAdmin.ValueOrDefault(false)
u.IsRestricted = isRestricted.ValueOrDefault(false) u.IsRestricted = isRestricted.ValueOrDefault(false)
if !createAndHandleCreatedUser(ctx, base.TplName(""), nil, u, overwriteDefault, &gothUser, setting.OAuth2Client.AccountLinking != setting.OAuth2AccountLinkingDisabled, auth.OAuth2) { if !createAndHandleCreatedUser(ctx, base.TplName(""), nil, u, overwriteDefault, &gothUser, setting.OAuth2Client.AccountLinking != setting.OAuth2AccountLinkingDisabled) {
// error already handled // error already handled
return return
} }
@@ -999,7 +999,7 @@ func SignInOAuthCallback(ctx *context.Context) {
} }
} else { } else {
// no existing user is found, request attach or new account // no existing user is found, request attach or new account
showLinkingLogin(ctx, gothUser, auth.OAuth2) showLinkingLogin(ctx, gothUser)
return return
} }
} }
@@ -1063,12 +1063,9 @@ func getUserAdminAndRestrictedFromGroupClaims(source *oauth2.Source, gothUser *g
return isAdmin, isRestricted return isAdmin, isRestricted
} }
func showLinkingLogin(ctx *context.Context, gothUser goth.User, authType auth.Type) { func showLinkingLogin(ctx *context.Context, gothUser goth.User) {
if err := updateSession(ctx, nil, map[string]any{ if err := updateSession(ctx, nil, map[string]any{
"linkAccountUser": auth.LinkAccountUser{ "linkAccountGothUser": gothUser,
Type: authType,
GothUser: gothUser,
},
}); err != nil { }); err != nil {
ctx.ServerError("updateSession", err) ctx.ServerError("updateSession", err)
return return
@@ -1147,7 +1144,7 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model
} }
// update external user information // update external user information
if err := externalaccount.UpdateExternalUser(ctx, u, gothUser, auth.OAuth2); err != nil { if err := externalaccount.UpdateExternalUser(ctx, u, gothUser); err != nil {
if !errors.Is(err, util.ErrNotExist) { if !errors.Is(err, util.ErrNotExist) {
log.Error("UpdateExternalUser failed: %v", err) log.Error("UpdateExternalUser failed: %v", err)
} }
+2 -3
View File
@@ -8,7 +8,6 @@ import (
"net/http" "net/http"
"net/url" "net/url"
auth_model "code.gitea.io/gitea/models/auth"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/auth/openid" "code.gitea.io/gitea/modules/auth/openid"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
@@ -364,7 +363,7 @@ func RegisterOpenIDPost(ctx *context.Context) {
Email: form.Email, Email: form.Email,
Passwd: password, Passwd: password,
} }
if !createUserInContext(ctx, tplSignUpOID, form, u, nil, nil, false, auth_model.NoType) { if !createUserInContext(ctx, tplSignUpOID, form, u, nil, nil, false) {
// error already handled // error already handled
return return
} }
@@ -380,7 +379,7 @@ func RegisterOpenIDPost(ctx *context.Context) {
return return
} }
if !handleUserCreated(ctx, u, nil, auth_model.NoType) { if !handleUserCreated(ctx, u, nil) {
// error already handled // error already handled
return return
} }
-172
View File
@@ -1,172 +0,0 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package auth
import (
"errors"
"fmt"
"net/http"
"strings"
"code.gitea.io/gitea/models/auth"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web/middleware"
"code.gitea.io/gitea/services/auth/source/saml"
"code.gitea.io/gitea/services/externalaccount"
"github.com/markbates/goth"
)
func SignInSAML(ctx *context.Context) {
provider := ctx.Params(":provider")
loginSource, err := auth.GetActiveAuthSourceByName(ctx, provider, auth.SAML)
if err != nil || loginSource == nil {
ctx.NotFound("SAMLMetadata", err)
return
}
if err = loginSource.Cfg.(*saml.Source).Callout(ctx.Req, ctx.Resp); err != nil {
if strings.Contains(err.Error(), "no provider for ") {
ctx.Error(http.StatusNotFound)
return
}
ctx.ServerError("SignIn", err)
}
}
func SignInSAMLCallback(ctx *context.Context) {
provider := ctx.Params(":provider")
loginSource, err := auth.GetActiveAuthSourceByName(ctx, provider, auth.SAML)
if err != nil || loginSource == nil {
ctx.NotFound("SignInSAMLCallback", err)
return
}
if loginSource == nil {
ctx.ServerError("SignIn", fmt.Errorf("no valid provider found, check configured callback url in provider"))
return
}
u, gothUser, err := samlUserLoginCallback(*ctx, loginSource, ctx.Req, ctx.Resp)
if err != nil {
ctx.ServerError("SignInSAMLCallback", err)
return
}
if u == nil {
if ctx.Doer != nil {
// attach user to already logged in user
err = externalaccount.LinkAccountToUser(ctx, ctx.Doer, gothUser, auth.SAML)
if err != nil {
ctx.ServerError("LinkAccountToUser", err)
return
}
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
return
} else if !setting.Service.AllowOnlyInternalRegistration && false {
// TODO: allow auto registration from saml users (OAuth2 uses the following setting.OAuth2Client.EnableAutoRegistration)
} else {
// no existing user is found, request attach or new account
showLinkingLogin(ctx, gothUser, auth.SAML)
return
}
}
handleSamlSignIn(ctx, loginSource, u, gothUser)
}
func handleSamlSignIn(ctx *context.Context, source *auth.Source, u *user_model.User, gothUser goth.User) {
if err := updateSession(ctx, nil, map[string]any{
"uid": u.ID,
"uname": u.Name,
}); err != nil {
ctx.ServerError("updateSession", err)
return
}
// Clear whatever CSRF cookie has right now, force to generate a new one
ctx.Csrf.DeleteCookie(ctx)
// Register last login
u.SetLastLogin()
// update external user information
if err := externalaccount.UpdateExternalUser(ctx, u, gothUser, auth.SAML); err != nil {
if !errors.Is(err, util.ErrNotExist) {
log.Error("UpdateExternalUser failed: %v", err)
}
}
if err := resetLocale(ctx, u); err != nil {
ctx.ServerError("resetLocale", err)
return
}
if redirectTo := ctx.GetSiteCookie("redirect_to"); len(redirectTo) > 0 {
middleware.DeleteRedirectToCookie(ctx.Resp)
ctx.RedirectToFirst(redirectTo)
return
}
ctx.Redirect(setting.AppSubURL + "/")
}
func samlUserLoginCallback(ctx context.Context, authSource *auth.Source, request *http.Request, response http.ResponseWriter) (*user_model.User, goth.User, error) {
samlSource := authSource.Cfg.(*saml.Source)
gothUser, err := samlSource.Callback(request, response)
if err != nil {
return nil, gothUser, err
}
user := &user_model.User{
LoginName: gothUser.UserID,
LoginType: auth.SAML,
LoginSource: authSource.ID,
}
hasUser, err := user_model.GetUser(ctx, user)
if err != nil {
return nil, goth.User{}, err
}
if hasUser {
return user, gothUser, nil
}
// search in external linked users
externalLoginUser := &user_model.ExternalLoginUser{
ExternalID: gothUser.UserID,
LoginSourceID: authSource.ID,
}
hasUser, err = user_model.GetExternalLogin(ctx, externalLoginUser)
if err != nil {
return nil, goth.User{}, err
}
if hasUser {
user, err = user_model.GetUserByID(request.Context(), externalLoginUser.UserID)
return user, gothUser, err
}
// no user found to login
return nil, gothUser, nil
}
func SAMLMetadata(ctx *context.Context) {
provider := ctx.Params(":provider")
loginSource, err := auth.GetActiveAuthSourceByName(ctx, provider, auth.SAML)
if err != nil || loginSource == nil {
ctx.NotFound("SAMLMetadata", err)
return
}
if err = loginSource.Cfg.(*saml.Source).Metadata(ctx.Req, ctx.Resp); err != nil {
ctx.ServerError("SAMLMetadata", err)
}
}
-5
View File
@@ -667,11 +667,6 @@ func registerRoutes(m *web.Route) {
m.Get("/{provider}", auth.SignInOAuth) m.Get("/{provider}", auth.SignInOAuth)
m.Get("/{provider}/callback", auth.SignInOAuthCallback) m.Get("/{provider}/callback", auth.SignInOAuthCallback)
}) })
m.Group("/saml", func() {
m.Get("/{provider}", auth.SignInSAML) // redir to SAML IDP
m.Post("/{provider}/acs", auth.SignInSAMLCallback)
m.Get("/{provider}/metadata", auth.SAMLMetadata)
})
}) })
// ***** END: User ***** // ***** END: User *****
@@ -1,22 +0,0 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package saml_test
import (
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/services/auth"
"code.gitea.io/gitea/services/auth/source/saml"
)
// This test file exists to assert that our Source exposes the interfaces that we expect
// It tightly binds the interfaces and implementation without breaking go import cycles
type sourceInterface interface {
auth_model.Config
auth_model.SourceSettable
auth_model.RegisterableSource
auth.PasswordAuthenticator
}
var _ (sourceInterface) = &saml.Source{}
-29
View File
@@ -1,29 +0,0 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package saml
import (
"context"
"sync"
"code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/modules/log"
)
var samlRWMutex = sync.RWMutex{}
func Init(ctx context.Context) error {
loginSources, _ := auth.GetActiveAuthProviderSources(ctx, auth.SAML)
for _, source := range loginSources {
samlSource, ok := source.Cfg.(*Source)
if !ok {
continue
}
err := samlSource.RegisterSource()
if err != nil {
log.Error("Unable to register source: %s due to Error: %v.", source.Name, err)
}
}
return nil
}
@@ -1,38 +0,0 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package saml
type NameIDFormat int
const (
SAML11Email NameIDFormat = iota + 1
SAML11Persistent
SAML11Unspecified
SAML20Email
SAML20Persistent
SAML20Transient
SAML20Unspecified
)
const DefaultNameIDFormat NameIDFormat = SAML20Persistent
var NameIDFormatNames = map[NameIDFormat]string{
SAML11Email: "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
SAML11Persistent: "urn:oasis:names:tc:SAML:1.1:nameid-format:persistent",
SAML11Unspecified: "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified",
SAML20Email: "urn:oasis:names:tc:SAML:2.0:nameid-format:emailAddress",
SAML20Persistent: "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
SAML20Transient: "urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
SAML20Unspecified: "urn:oasis:names:tc:SAML:2.0:nameid-format:unspecified",
}
// String returns the name of the NameIDFormat
func (n NameIDFormat) String() string {
return NameIDFormatNames[n]
}
// Int returns the int value of the NameIDFormat
func (n NameIDFormat) Int() int {
return int(n)
}
-109
View File
@@ -1,109 +0,0 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package saml
import (
"context"
"fmt"
"html"
"html/template"
"io"
"net/http"
"sort"
"time"
"code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/svg"
"code.gitea.io/gitea/modules/util"
)
// Providers is list of known/available providers.
type Providers map[string]Source
var providers = Providers{}
// Provider is an interface for describing a single SAML provider
type Provider interface {
Name() string
IconHTML(size int) template.HTML
}
// AuthSourceProvider is a SAML provider
type AuthSourceProvider struct {
sourceName, iconURL string
}
func (p *AuthSourceProvider) Name() string {
return p.sourceName
}
func (p *AuthSourceProvider) IconHTML(size int) template.HTML {
if p.iconURL != "" {
return template.HTML(fmt.Sprintf(`<img class="gt-object-contain gt-mr-3" width="%d" height="%d" src="%s" alt="%s">`,
size,
size,
html.EscapeString(p.iconURL), html.EscapeString(p.Name()),
))
}
return svg.RenderHTML("gitea-lock-cog", size, "gt-mr-3")
}
func readIdentityProviderMetadata(ctx context.Context, source *Source) ([]byte, error) {
if source.IdentityProviderMetadata != "" {
return []byte(source.IdentityProviderMetadata), nil
}
req := httplib.NewRequest(source.IdentityProviderMetadataURL, "GET")
req.SetTimeout(20*time.Second, time.Minute)
resp, err := req.Response()
if err != nil {
return nil, fmt.Errorf("Unable to contact gitea: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, err
}
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return data, nil
}
func createProviderFromSource(source *auth.Source) (Provider, error) {
samlCfg, ok := source.Cfg.(*Source)
if !ok {
return nil, fmt.Errorf("invalid SAML source config: %v", samlCfg)
}
return &AuthSourceProvider{sourceName: source.Name, iconURL: samlCfg.IconURL}, nil
}
// GetSAMLProviders returns the list of configured SAML providers
func GetSAMLProviders(ctx context.Context, isActive util.OptionalBool) ([]Provider, error) {
authSources, err := db.Find[auth.Source](ctx, auth.FindSourcesOptions{
IsActive: isActive,
LoginType: auth.SAML,
})
if err != nil {
return nil, err
}
samlProviders := make([]Provider, 0, len(authSources))
for _, source := range authSources {
p, err := createProviderFromSource(source)
if err != nil {
return nil, err
}
samlProviders = append(samlProviders, p)
}
sort.Slice(samlProviders, func(i, j int) bool {
return samlProviders[i].Name() < samlProviders[j].Name()
})
return samlProviders, nil
}
-202
View File
@@ -1,202 +0,0 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package saml
import (
"context"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"encoding/xml"
"errors"
"fmt"
"math/big"
"net/url"
"time"
"code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
saml2 "github.com/russellhaering/gosaml2"
"github.com/russellhaering/gosaml2/types"
dsig "github.com/russellhaering/goxmldsig"
)
// Source holds configuration for the SAML login source.
type Source struct {
// IdentityProviderMetadata description: The SAML Identity Provider metadata XML contents (for static configuration of the SAML Service Provider). The value of this field should be an XML document whose root element is `<EntityDescriptor>` or `<EntityDescriptors>`. To escape the value into a JSON string, you may want to use a tool like https://json-escape-text.now.sh.
IdentityProviderMetadata string
// IdentityProviderMetadataURL description: The SAML Identity Provider metadata URL (for dynamic configuration of the SAML Service Provider).
IdentityProviderMetadataURL string
// InsecureSkipAssertionSignatureValidation description: Whether the Service Provider should (insecurely) accept assertions from the Identity Provider without a valid signature.
InsecureSkipAssertionSignatureValidation bool
// NameIDFormat description: The SAML NameID format to use when performing user authentication.
NameIDFormat NameIDFormat
// ServiceProviderCertificate description: The SAML Service Provider certificate in X.509 encoding (begins with "-----BEGIN CERTIFICATE-----"). This certificate is used by the Identity Provider to validate the Service Provider's AuthnRequests and LogoutRequests. It corresponds to the Service Provider's private key (`serviceProviderPrivateKey`). To escape the value into a JSON string, you may want to use a tool like https://json-escape-text.now.sh.
ServiceProviderCertificate string
// ServiceProviderIssuer description: The SAML Service Provider name, used to identify this Service Provider. This is required if the "externalURL" field is not set (as the SAML metadata endpoint is computed as "<externalURL>.auth/saml/metadata"), or when using multiple SAML authentication providers.
ServiceProviderIssuer string
// ServiceProviderPrivateKey description: The SAML Service Provider private key in PKCS#8 encoding (begins with "-----BEGIN PRIVATE KEY-----"). This private key is used to sign AuthnRequests and LogoutRequests. It corresponds to the Service Provider's certificate (`serviceProviderCertificate`). To escape the value into a JSON string, you may want to use a tool like https://json-escape-text.now.sh.
ServiceProviderPrivateKey string
CallbackURL string
IconURL string
// EmailAssertionKey description: Assertion key for user.Email
EmailAssertionKey string
// NameAssertionKey description: Assertion key for user.NickName
NameAssertionKey string
// UsernameAssertionKey description: Assertion key for user.Name
UsernameAssertionKey string
// reference to the authSource
authSource *auth.Source
samlSP *saml2.SAMLServiceProvider
}
func GenerateSAMLSPKeypair() (string, string, error) {
key, err := rsa.GenerateKey(rand.Reader, 4096)
if err != nil {
return "", "", err
}
keyBytes := x509.MarshalPKCS1PrivateKey(key)
keyPem := pem.EncodeToMemory(
&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: keyBytes,
},
)
now := time.Now()
template := &x509.Certificate{
SerialNumber: big.NewInt(0),
NotBefore: now.Add(-5 * time.Minute),
NotAfter: now.Add(365 * 24 * time.Hour),
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{},
BasicConstraintsValid: true,
}
certificate, err := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
if err != nil {
return "", "", err
}
certPem := pem.EncodeToMemory(
&pem.Block{
Type: "CERTIFICATE",
Bytes: certificate,
},
)
return string(keyPem), string(certPem), nil
}
func (source *Source) initSAMLSp() error {
source.CallbackURL = setting.AppURL + "user/saml/" + url.PathEscape(source.authSource.Name) + "/acs"
idpMetadata, err := readIdentityProviderMetadata(context.Background(), source)
if err != nil {
return err
}
{
if source.IdentityProviderMetadataURL != "" {
log.Trace(fmt.Sprintf("Identity Provider metadata: %s", source.IdentityProviderMetadataURL), string(idpMetadata))
}
}
metadata := &types.EntityDescriptor{}
err = xml.Unmarshal(idpMetadata, metadata)
if err != nil {
return err
}
certStore := dsig.MemoryX509CertificateStore{
Roots: []*x509.Certificate{},
}
if metadata.IDPSSODescriptor == nil {
return errors.New("saml idp metadata missing IDPSSODescriptor")
}
for _, kd := range metadata.IDPSSODescriptor.KeyDescriptors {
for idx, xcert := range kd.KeyInfo.X509Data.X509Certificates {
if xcert.Data == "" {
return fmt.Errorf("metadata certificate(%d) must not be empty", idx)
}
certData, err := base64.StdEncoding.DecodeString(xcert.Data)
if err != nil {
return err
}
idpCert, err := x509.ParseCertificate(certData)
if err != nil {
return err
}
certStore.Roots = append(certStore.Roots, idpCert)
}
}
var keyStore dsig.X509KeyStore
if source.ServiceProviderCertificate != "" && source.ServiceProviderPrivateKey != "" {
keyPair, err := tls.X509KeyPair([]byte(source.ServiceProviderCertificate), []byte(source.ServiceProviderPrivateKey))
if err != nil {
return err
}
keyPair.Leaf, err = x509.ParseCertificate(keyPair.Certificate[0])
if err != nil {
return err
}
keyStore = dsig.TLSCertKeyStore(keyPair)
}
source.samlSP = &saml2.SAMLServiceProvider{
IdentityProviderSSOURL: metadata.IDPSSODescriptor.SingleSignOnServices[0].Location,
IdentityProviderIssuer: metadata.EntityID,
AudienceURI: setting.AppURL + "user/saml/" + url.PathEscape(source.authSource.Name) + "/metadata",
AssertionConsumerServiceURL: source.CallbackURL,
SkipSignatureValidation: source.InsecureSkipAssertionSignatureValidation,
NameIdFormat: source.NameIDFormat.String(),
IDPCertificateStore: &certStore,
SignAuthnRequests: source.ServiceProviderCertificate != "" && source.ServiceProviderPrivateKey != "",
SPKeyStore: keyStore,
ServiceProviderIssuer: setting.AppURL + "user/saml/" + url.PathEscape(source.authSource.Name) + "/metadata",
}
return nil
}
// FromDB fills up a SAML from serialized format.
func (source *Source) FromDB(bs []byte) error {
if err := json.UnmarshalHandleDoubleEncode(bs, &source); err != nil {
return err
}
return source.initSAMLSp()
}
// ToDB exports a SAML to a serialized format.
func (source *Source) ToDB() ([]byte, error) {
return json.Marshal(source)
}
// SetAuthSource sets the related AuthSource
func (source *Source) SetAuthSource(authSource *auth.Source) {
source.authSource = authSource
}
func init() {
auth.RegisterTypeConfig(auth.SAML, &Source{})
}
@@ -1,16 +0,0 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package saml
import (
"context"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/services/auth/source/db"
)
// Authenticate falls back to the db authenticator
func (source *Source) Authenticate(ctx context.Context, user *user_model.User, login, password string) (*user_model.User, error) {
return db.Authenticate(ctx, user, login, password)
}
@@ -1,89 +0,0 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package saml
import (
"fmt"
"net/http"
"strings"
"github.com/markbates/goth"
)
// Callout redirects request/response pair to authenticate against the provider
func (source *Source) Callout(request *http.Request, response http.ResponseWriter) error {
samlRWMutex.RLock()
defer samlRWMutex.RUnlock()
if _, ok := providers[source.authSource.Name]; !ok {
return fmt.Errorf("no provider for this saml")
}
authURL, err := providers[source.authSource.Name].samlSP.BuildAuthURL("")
if err == nil {
http.Redirect(response, request, authURL, http.StatusTemporaryRedirect)
}
return err
}
// Callback handles SAML callback, resolve to a goth user and send back to original url
// this will trigger a new authentication request, but because we save it in the session we can use that
func (source *Source) Callback(request *http.Request, response http.ResponseWriter) (goth.User, error) {
samlRWMutex.RLock()
defer samlRWMutex.RUnlock()
user := goth.User{
Provider: source.authSource.Name,
}
samlResponse := request.FormValue("SAMLResponse")
assertions, err := source.samlSP.RetrieveAssertionInfo(samlResponse)
if err != nil {
return user, err
}
if assertions.WarningInfo.OneTimeUse {
return user, fmt.Errorf("SAML response contains one time use warning")
}
if assertions.WarningInfo.ProxyRestriction != nil {
return user, fmt.Errorf("SAML response contains proxy restriction warning: %v", assertions.WarningInfo.ProxyRestriction)
}
if assertions.WarningInfo.NotInAudience {
return user, fmt.Errorf("SAML response contains audience warning")
}
if assertions.WarningInfo.InvalidTime {
return user, fmt.Errorf("SAML response contains invalid time warning")
}
samlMap := make(map[string]string)
for key, value := range assertions.Values {
keyParsed := strings.ToLower(key[strings.LastIndex(key, "/")+1:]) // Uses the trailing slug as the key name.
valueParsed := value.Values[0].Value
samlMap[keyParsed] = valueParsed
}
user.UserID = assertions.NameID
if user.UserID == "" {
return user, fmt.Errorf("no nameID found in SAML response")
}
// email
if _, ok := samlMap[source.EmailAssertionKey]; !ok {
user.Email = samlMap[source.EmailAssertionKey]
}
// name
if _, ok := samlMap[source.NameAssertionKey]; !ok {
user.NickName = samlMap[source.NameAssertionKey]
}
// username
if _, ok := samlMap[source.UsernameAssertionKey]; !ok {
user.Name = samlMap[source.UsernameAssertionKey]
}
// TODO: utilize groups once mapping is supported
return user, nil
}
@@ -1,32 +0,0 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package saml
import (
"encoding/xml"
"fmt"
"net/http"
)
// Metadata redirects request/response pair to authenticate against the provider
func (source *Source) Metadata(request *http.Request, response http.ResponseWriter) error {
samlRWMutex.RLock()
defer samlRWMutex.RUnlock()
if _, ok := providers[source.authSource.Name]; !ok {
return fmt.Errorf("provider does not exist")
}
metadata, err := providers[source.authSource.Name].samlSP.Metadata()
if err != nil {
return err
}
buf, err := xml.Marshal(metadata)
if err != nil {
return err
}
response.Header().Set("Content-Type", "application/samlmetadata+xml; charset=utf-8")
_, _ = response.Write(buf)
return nil
}
@@ -1,23 +0,0 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package saml
// RegisterSource causes an OAuth2 configuration to be registered
func (source *Source) RegisterSource() error {
samlRWMutex.Lock()
defer samlRWMutex.Unlock()
if err := source.initSAMLSp(); err != nil {
return err
}
providers[source.authSource.Name] = *source
return nil
}
// UnregisterSource causes an SAML configuration to be unregistered
func (source *Source) UnregisterSource() error {
samlRWMutex.Lock()
defer samlRWMutex.Unlock()
delete(providers, source.authSource.Name)
return nil
}
+5 -6
View File
@@ -7,8 +7,9 @@ import (
"context" "context"
"fmt" "fmt"
"code.gitea.io/gitea/models/auth"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"github.com/markbates/goth"
) )
// Store represents a thing that stores things // Store represents a thing that stores things
@@ -20,12 +21,10 @@ type Store interface {
// LinkAccountFromStore links the provided user with a stored external user // LinkAccountFromStore links the provided user with a stored external user
func LinkAccountFromStore(ctx context.Context, store Store, user *user_model.User) error { func LinkAccountFromStore(ctx context.Context, store Store, user *user_model.User) error {
externalLinkUserInterface := store.Get("linkAccountUser") gothUser := store.Get("linkAccountGothUser")
if externalLinkUserInterface == nil { if gothUser == nil {
return fmt.Errorf("not in LinkAccount session") return fmt.Errorf("not in LinkAccount session")
} }
externalLinkUser := externalLinkUserInterface.(auth.LinkAccountUser) return LinkAccountToUser(ctx, user, gothUser.(goth.User))
return LinkAccountToUser(ctx, user, externalLinkUser.GothUser, externalLinkUser.Type)
} }
+6 -6
View File
@@ -16,8 +16,8 @@ import (
"github.com/markbates/goth" "github.com/markbates/goth"
) )
func toExternalLoginUser(ctx context.Context, user *user_model.User, gothUser goth.User, authType auth.Type) (*user_model.ExternalLoginUser, error) { func toExternalLoginUser(ctx context.Context, user *user_model.User, gothUser goth.User) (*user_model.ExternalLoginUser, error) {
authSource, err := auth.GetActiveAuthSourceByName(ctx, gothUser.Provider, authType) authSource, err := auth.GetActiveOAuth2SourceByName(ctx, gothUser.Provider)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -43,8 +43,8 @@ func toExternalLoginUser(ctx context.Context, user *user_model.User, gothUser go
} }
// LinkAccountToUser link the gothUser to the user // LinkAccountToUser link the gothUser to the user
func LinkAccountToUser(ctx context.Context, user *user_model.User, gothUser goth.User, authType auth.Type) error { func LinkAccountToUser(ctx context.Context, user *user_model.User, gothUser goth.User) error {
externalLoginUser, err := toExternalLoginUser(ctx, user, gothUser, authType) externalLoginUser, err := toExternalLoginUser(ctx, user, gothUser)
if err != nil { if err != nil {
return err return err
} }
@@ -71,8 +71,8 @@ func LinkAccountToUser(ctx context.Context, user *user_model.User, gothUser goth
} }
// UpdateExternalUser updates external user's information // UpdateExternalUser updates external user's information
func UpdateExternalUser(ctx context.Context, user *user_model.User, gothUser goth.User, authType auth.Type) error { func UpdateExternalUser(ctx context.Context, user *user_model.User, gothUser goth.User) error {
externalLoginUser, err := toExternalLoginUser(ctx, user, gothUser, authType) externalLoginUser, err := toExternalLoginUser(ctx, user, gothUser)
if err != nil { if err != nil {
return err return err
} }
+1 -14
View File
@@ -1,4 +1,3 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// Copyright 2014 The Gogs Authors. All rights reserved. // Copyright 2014 The Gogs Authors. All rights reserved.
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
@@ -16,7 +15,7 @@ import (
// AuthenticationForm form for authentication // AuthenticationForm form for authentication
type AuthenticationForm struct { type AuthenticationForm struct {
ID int64 ID int64
Type int `binding:"Range(2,9)"` Type int `binding:"Range(2,7)"`
Name string `binding:"Required;MaxSize(30)"` Name string `binding:"Required;MaxSize(30)"`
Host string Host string
Port int Port int
@@ -83,18 +82,6 @@ type AuthenticationForm struct {
SSPIDefaultLanguage string SSPIDefaultLanguage string
GroupTeamMap string `binding:"ValidGroupTeamMap"` GroupTeamMap string `binding:"ValidGroupTeamMap"`
GroupTeamMapRemoval bool GroupTeamMapRemoval bool
// SAML Settings
NameIDFormat int
IdentityProviderMetadata string
IdentityProviderMetadataURL string
InsecureSkipAssertionSignatureValidation bool
ServiceProviderCertificate string
ServiceProviderPrivateKey string
EmailAssertionKey string
NameAssertionKey string
UsernameAssertionKey string
SAMLIconURL string
} }
// Validate validates fields // Validate validates fields
-66
View File
@@ -367,69 +367,6 @@
</div> </div>
{{end}} {{end}}
<!-- SAML -->
{{if .Source.IsSAML}}
{{$cfg:=.Source.Cfg}}
<div class="inline required field">
<label>{{ctx.Locale.Tr "admin.auths.saml_nameidformat"}}</label>
<div class="ui selection type dropdown">
<input type="hidden" id="name_id_format" name="name_id_format" value="{{$cfg.NameIDFormat}}">
<div class="text">{{.CurrentNameIDFormat}}</div>
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
<div class="menu">
{{range .NameIDFormats}}
<div class="item" data-value="{{.Type.Int}}">{{.Name}}</div>
{{end}}
</div>
</div>
</div>
<div class="optional field">
<label for="saml_icon_url">{{ctx.Locale.Tr "admin.auths.saml_icon_url"}}</label>
<input id="saml_icon_url" name="saml_icon_url" value="{{$cfg.IconURL}}">
</div>
<div class="field">
<label for="identity_provider_metadata_url">{{ctx.Locale.Tr "admin.auths.saml_identity_provider_metadata_url"}}</label>
<input id="identity_provider_metadata_url" name="identity_provider_metadata_url" value="{{$cfg.IdentityProviderMetadataURL}}">
</div>
<div class="field">
<label for="identity_provider_metadata">{{ctx.Locale.Tr "admin.auths.saml_identity_provider_metadata"}}</label>
<textarea rows=2 id="identity_provider_metadata" name="identity_provider_metadata">{{$cfg.IdentityProviderMetadata}}</textarea>
</div>
<div class="inline field">
<div class="ui checkbox">
<label><strong>{{ctx.Locale.Tr "admin.auths.saml_insecure_skip_assertion_signature_validation"}}</strong></label>
<input name="insecure_skip_assertion_signature_validation" type="checkbox" {{if $cfg.InsecureSkipAssertionSignatureValidation}}checked{{end}}>
</div>
</div>
<div class=" field">
<label for="service_provider_certificate">{{ctx.Locale.Tr "admin.auths.saml_service_provider_certificate"}}</label>
<textarea rows=2 id="service_provider_certificate" name="service_provider_certificate">{{$cfg.ServiceProviderCertificate}}</textarea>
</div>
<div class=" field">
<label for="service_provider_private_key">{{ctx.Locale.Tr "admin.auths.saml_service_provider_private_key"}}</label>
<textarea rows=2 id="service_provider_private_key" name="service_provider_private_key">{{$cfg.ServiceProviderPrivateKey}}</textarea>
</div>
<div class="field">
<label for="email_assertion_key">{{ctx.Locale.Tr "admin.auths.saml_identity_provider_email_assertion_key"}}</label>
<input id="email_assertion_key" name="email_assertion_key" value="{{if not $cfg.EmailAssertionKey}}http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress{{else}}{{$cfg.EmailAssertionKey}}{{end}}">
</div>
<div class="field">
<label for="name_assertion_key">{{ctx.Locale.Tr "admin.auths.saml_identity_provider_name_assertion_key"}}</label>
<input id="name_assertion_key" name="name_assertion_key" value="{{if not $cfg.NameAssertionKey}}http://schemas.xmlsoap.org/claims/CommonName{{else}}{{$cfg.NameAssertionKey}}{{end}}">
</div>
<div class="field">
<label for="username_assertion_key">{{ctx.Locale.Tr "admin.auths.saml_identity_provider_username_assertion_key"}}</label>
<input id="username_assertion_key" name="username_assertion_key" value="{{if not $cfg.UsernameAssertionKey}}http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name{{else}}{{$cfg.UsernameAssertionKey}}{{end}}">
</div>
{{end}}
<!-- SSPI --> <!-- SSPI -->
{{if .Source.IsSSPI}} {{if .Source.IsSSPI}}
{{$cfg:=.Source.Cfg}} {{$cfg:=.Source.Cfg}}
@@ -504,9 +441,6 @@
<h5>GMail Settings:</h5> <h5>GMail Settings:</h5>
<p>Host: smtp.gmail.com, Port: 587, Enable TLS Encryption: true</p> <p>Host: smtp.gmail.com, Port: 587, Enable TLS Encryption: true</p>
<h5>SAML Settings:</h5>
<p>{{ctx.Locale.Tr "admin.auths.tips.saml"}}</p>
<h5 class="oauth2">{{ctx.Locale.Tr "admin.auths.tips.oauth2.general"}}:</h5> <h5 class="oauth2">{{ctx.Locale.Tr "admin.auths.tips.oauth2.general"}}:</h5>
<p class="oauth2">{{ctx.Locale.Tr "admin.auths.tips.oauth2.general.tip"}} <b id="oauth2-callback-url"></b></p> <p class="oauth2">{{ctx.Locale.Tr "admin.auths.tips.oauth2.general.tip"}} <b id="oauth2-callback-url"></b></p>
</div> </div>
-6
View File
@@ -53,9 +53,6 @@
<!-- SSPI --> <!-- SSPI -->
{{template "admin/auth/source/sspi" .}} {{template "admin/auth/source/sspi" .}}
<!-- SAML -->
{{template "admin/auth/source/saml" .}}
<div class="ldap field"> <div class="ldap field">
<div class="ui checkbox"> <div class="ui checkbox">
<label><strong>{{ctx.Locale.Tr "admin.auths.attributes_in_bind"}}</strong></label> <label><strong>{{ctx.Locale.Tr "admin.auths.attributes_in_bind"}}</strong></label>
@@ -88,9 +85,6 @@
<h5>GMail Settings:</h5> <h5>GMail Settings:</h5>
<p>Host: smtp.gmail.com, Port: 587, Enable TLS Encryption: true</p> <p>Host: smtp.gmail.com, Port: 587, Enable TLS Encryption: true</p>
<h5>SAML Settings:</h5>
<p>{{ctx.Locale.Tr "admin.auths.tips.saml"}}</p>
<h5 class="oauth2">{{ctx.Locale.Tr "admin.auths.tips.oauth2.general"}}:</h5> <h5 class="oauth2">{{ctx.Locale.Tr "admin.auths.tips.oauth2.general"}}:</h5>
<p class="oauth2">{{ctx.Locale.Tr "admin.auths.tips.oauth2.general.tip"}} <b id="oauth2-callback-url"></b></p> <p class="oauth2">{{ctx.Locale.Tr "admin.auths.tips.oauth2.general.tip"}} <b id="oauth2-callback-url"></b></p>
-62
View File
@@ -1,62 +0,0 @@
<div class="saml field {{if not (eq .type 8)}}gt-hidden{{end}}">
<div class="inline required field">
<label>{{ctx.Locale.Tr "admin.auths.saml_nameidformat"}}</label>
<div class="ui selection type dropdown">
<input type="hidden" id="name_id_format" name="name_id_format" value="{{.name_id_format}}">
<div class="text">{{.CurrentNameIDFormat}}</div>
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
<div class="menu">
{{range .NameIDFormats}}
<div class="item" data-value="{{.Type.Int}}">{{.Name}}</div>
{{end}}
</div>
</div>
</div>
<div class="optional field">
<label for="saml_icon_url">{{ctx.Locale.Tr "admin.auths.saml_icon_url"}}</label>
<input id="saml_icon_url" name="saml_icon_url" value="{{.SAMLIconURL}}">
</div>
<div class="field">
<label for="identity_provider_metadata_url">{{ctx.Locale.Tr "admin.auths.saml_identity_provider_metadata_url"}}</label>
<input id="identity_provider_metadata_url" name="identity_provider_metadata_url" value="{{.IdentityProviderMetadataURL}}">
</div>
<div class="field">
<label for="identity_provider_metadata">{{ctx.Locale.Tr "admin.auths.saml_identity_provider_metadata"}}</label>
<textarea rows=2 id="identity_provider_metadata" name="identity_provider_metadata" value="{{.IdentityProviderMetadata}}"></textarea>
</div>
<div class="inline field">
<div class="ui checkbox">
<label><strong>{{ctx.Locale.Tr "admin.auths.saml_insecure_skip_assertion_signature_validation"}}</strong></label>
<input name="insecure_skip_assertion_signature_validation" type="checkbox" {{if .InsecureSkipAssertionSignatureValidation}}checked{{end}}>
</div>
</div>
<div class="field">
<label for="service_provider_certificate">{{ctx.Locale.Tr "admin.auths.saml_service_provider_certificate"}}</label>
<textarea rows=2 id="service_provider_certificate" name="service_provider_certificate" value="{{.ServiceProviderCertificate}}"></textarea>
</div>
<div class="field">
<label for="service_provider_private_key">{{ctx.Locale.Tr "admin.auths.saml_service_provider_private_key"}}</label>
<textarea rows=2 id="service_provider_private_key" name="service_provider_private_key" value="{{.ServiceProviderPrivateKey}}"></textarea>
</div>
<div class="field">
<label for="email_assertion_key">{{ctx.Locale.Tr "admin.auths.saml_identity_provider_email_assertion_key"}}</label>
<input id="email_assertion_key" name="email_assertion_key" value="{{if not .EmailAssertionKey}}http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress{{else}}{{.EmailAssertionKey}}{{end}}">
</div>
<div class="field">
<label for="name_assertion_key">{{ctx.Locale.Tr "admin.auths.saml_identity_provider_name_assertion_key"}}</label>
<input id="name_assertion_key" name="name_assertion_key" value="{{if not .NameAssertionKey}}http://schemas.xmlsoap.org/claims/CommonName{{else}}{{.NameAssertionKey}}{{end}}">
</div>
<div class="field">
<label for="username_assertion_key">{{ctx.Locale.Tr "admin.auths.saml_identity_provider_username_assertion_key"}}</label>
<input id="username_assertion_key" name="username_assertion_key" value="{{if not .UsernameAssertionKey}}http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name{{else}}{{.UsernameAssertionKey}}{{end}}">
</div>
</div>
-17
View File
@@ -69,22 +69,5 @@
</div> </div>
</div> </div>
{{end}} {{end}}
{{if .SAMLProviders}}
<div class="divider divider-text">
{{.locale.Tr "sign_in_or"}}
</div>
<div id="saml-login-navigator" class="gt-py-2">
<div class="gt-df gt-fc gt-jc">
<div id="saml-login-navigator-inner" class="gt-df gt-fc gt-fw gt-ac gt-gap-3">
{{range $provider := .SAMLProviders}}
<a class="{{$provider.Name}} ui button gt-df gt-ac gt-jc gt-py-3 saml-login-link" href="{{AppSubUrl}}/user/saml/{{$provider.Name}}">
{{.IconHTML 28}}
{{ctx.Locale.Tr "sign_in_with_provider" $provider.Name}}
</a>
{{end}}
</div>
</div>
</div>
{{end}}
</form> </form>
</div> </div>
-17
View File
@@ -110,20 +110,3 @@ SLOW_FLUSH = 5S ; 5s is the default value
```bash ```bash
GITEA_SLOW_TEST_TIME="10s" GITEA_SLOW_FLUSH_TIME="5s" make test-sqlite GITEA_SLOW_TEST_TIME="10s" GITEA_SLOW_FLUSH_TIME="5s" make test-sqlite
``` ```
## Running SimpleSAML for testing SAML locally
```shell
docker run \
-p 8080:8080 \
-p 8443:8443 \
-e SIMPLESAMLPHP_SP_ENTITY_ID=http://localhost:3003/user/saml/test-sp/metadata \
-e SIMPLESAMLPHP_SP_ASSERTION_CONSUMER_SERVICE=http://localhost:3003/user/saml/test-sp/acs \
-e SIMPLESAMLPHP_SP_SINGLE_LOGOUT_SERVICE=http://localhost:3003/user/saml/test-sp/acs \
--add-host=localhost:192.168.65.2 \
-d allspice/simple-saml
```
```shell
TEST_SIMPLESAML_URL=localhost:8080 make test-sqlite#TestSAMLRegistration
```
-150
View File
@@ -1,150 +0,0 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package integration
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io"
"net/http"
"net/http/cookiejar"
"net/url"
"os"
"regexp"
"strings"
"testing"
"time"
"code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/services/auth/source/saml"
"code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert"
)
func TestSAMLRegistration(t *testing.T) {
defer tests.PrepareTestEnv(t)()
samlURL := "localhost:8080"
if os.Getenv("CI") == "" || !setting.Database.Type.IsPostgreSQL() {
// Make it possible to run tests against a local simplesaml instance
samlURL = os.Getenv("TEST_SIMPLESAML_URL")
if samlURL == "" {
t.Skip("TEST_SIMPLESAML_URL not set and not running in CI")
return
}
}
privateKey, cert, err := saml.GenerateSAMLSPKeypair()
assert.NoError(t, err)
// verify that the keypair can be parsed
keyPair, err := tls.X509KeyPair([]byte(cert), []byte(privateKey))
assert.NoError(t, err)
keyPair.Leaf, err = x509.ParseCertificate(keyPair.Certificate[0])
assert.NoError(t, err)
assert.NoError(t, auth.CreateSource(db.DefaultContext, &auth.Source{
Type: auth.SAML,
Name: "test-sp",
IsActive: true,
IsSyncEnabled: false,
Cfg: &saml.Source{
IdentityProviderMetadata: "",
IdentityProviderMetadataURL: fmt.Sprintf("http://%s/simplesaml/saml2/idp/metadata.php", samlURL),
InsecureSkipAssertionSignatureValidation: false,
NameIDFormat: 4,
ServiceProviderCertificate: "", // SimpleSAMLPhp requires that the SP certificate be specified in the server configuration rather than SP metadata
ServiceProviderPrivateKey: "",
EmailAssertionKey: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress",
NameAssertionKey: "http://schemas.xmlsoap.org/claims/CommonName",
UsernameAssertionKey: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name",
IconURL: "",
},
}))
// check the saml metadata url
req := NewRequest(t, "GET", "/user/saml/test-sp/metadata")
MakeRequest(t, req, http.StatusOK)
req = NewRequest(t, "GET", "/user/saml/test-sp")
resp := MakeRequest(t, req, http.StatusTemporaryRedirect)
jar, err := cookiejar.New(nil)
assert.NoError(t, err)
client := http.Client{
Timeout: 30 * time.Second,
Jar: jar,
}
httpReq, err := http.NewRequest("GET", test.RedirectURL(resp), nil)
assert.NoError(t, err)
var formRedirectURL *url.URL
client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
// capture the redirected destination to use in POST request
formRedirectURL = req.URL
return nil
}
res, err := client.Do(httpReq)
client.CheckRedirect = nil
assert.NoError(t, err)
assert.Equal(t, http.StatusOK, res.StatusCode)
assert.NotNil(t, formRedirectURL)
form := url.Values{
"username": {"user1"},
"password": {"user1pass"},
}
httpReq, err = http.NewRequest("POST", formRedirectURL.String(), strings.NewReader(form.Encode()))
assert.NoError(t, err)
httpReq.Header.Add("Content-Type", "application/x-www-form-urlencoded")
res, err = client.Do(httpReq)
assert.NoError(t, err)
assert.Equal(t, http.StatusOK, res.StatusCode)
body, err := io.ReadAll(res.Body)
assert.NoError(t, err)
samlResMatcher := regexp.MustCompile(`<input.*?name="SAMLResponse".*?value="([^"]+)".*?>`)
matches := samlResMatcher.FindStringSubmatch(string(body))
assert.Len(t, matches, 2)
assert.NoError(t, res.Body.Close())
session := emptyTestSession(t)
req = NewRequestWithValues(t, "POST", "/user/saml/test-sp/acs", map[string]string{
"SAMLResponse": matches[1],
})
resp = session.MakeRequest(t, req, http.StatusSeeOther)
assert.Equal(t, test.RedirectURL(resp), "/user/link_account")
csrf := GetCSRF(t, session, test.RedirectURL(resp))
// link the account
req = NewRequestWithValues(t, "POST", "/user/link_account_signup", map[string]string{
"_csrf": csrf,
"user_name": "samluser",
"email": "saml@example.com",
})
resp = session.MakeRequest(t, req, http.StatusSeeOther)
assert.Equal(t, test.RedirectURL(resp), "/")
// verify that the user was created
u, err := user_model.GetUserByEmail(db.DefaultContext, "saml@example.com")
assert.NoError(t, err)
assert.NotNil(t, u)
assert.Equal(t, "samluser", u.Name)
}
+2 -6
View File
@@ -103,9 +103,9 @@ export function initAdminCommon() {
// New authentication // New authentication
if ($('.admin.new.authentication').length > 0) { if ($('.admin.new.authentication').length > 0) {
$('#auth_type').on('change', function () { $('#auth_type').on('change', function () {
hideElem($('.ldap, .dldap, .smtp, .pam, .oauth2, .has-tls, .search-page-size, .sspi, .saml')); hideElem($('.ldap, .dldap, .smtp, .pam, .oauth2, .has-tls, .search-page-size, .sspi'));
$('.ldap input[required], .binddnrequired input[required], .dldap input[required], .smtp input[required], .pam input[required], .oauth2 input[required], .has-tls input[required], .sspi input[required], .saml input[required]').removeAttr('required'); $('.ldap input[required], .binddnrequired input[required], .dldap input[required], .smtp input[required], .pam input[required], .oauth2 input[required], .has-tls input[required], .sspi input[required]').removeAttr('required');
$('.binddnrequired').removeClass('required'); $('.binddnrequired').removeClass('required');
const authType = $(this).val(); const authType = $(this).val();
@@ -137,10 +137,6 @@ export function initAdminCommon() {
showElem($('.sspi')); showElem($('.sspi'));
$('.sspi div.required input').attr('required', 'required'); $('.sspi div.required input').attr('required', 'required');
break; break;
case '8': // SAML
showElem($('.saml'));
$('.saml div.required input').attr('required', 'required');
break;
} }
if (authType === '2' || authType === '5') { if (authType === '2' || authType === '5') {
onSecurityProtocolChange(); onSecurityProtocolChange();
-21
View File
@@ -20,24 +20,3 @@ export function initUserAuthOauth2() {
}); });
} }
} }
export function initUserAuthSAML() {
const outer = document.getElementById('saml-login-navigator');
if (!outer) return;
const inner = document.getElementById('saml-login-navigator-inner');
checkAppUrl();
for (const link of outer.querySelectorAll('.saml-login-link')) {
link.addEventListener('click', () => {
inner.classList.add('gt-invisible');
outer.classList.add('is-loading');
setTimeout(() => {
// recover previous content to let user try again
// usually redirection will be performed before this action
outer.classList.remove('is-loading');
inner.classList.remove('gt-invisible');
}, 5000);
});
}
}
+1 -5
View File
@@ -23,10 +23,7 @@ import {initFindFileInRepo} from './features/repo-findfile.js';
import {initCommentContent, initMarkupContent} from './markup/content.js'; import {initCommentContent, initMarkupContent} from './markup/content.js';
import {initPdfViewer} from './render/pdf.js'; import {initPdfViewer} from './render/pdf.js';
import { import {initUserAuthOauth2} from './features/user-auth.js';
initUserAuthOauth2,
initUserAuthSAML
} from './features/user-auth.js';
import { import {
initRepoIssueDue, initRepoIssueDue,
initRepoIssueReferenceRepositorySearch, initRepoIssueReferenceRepositorySearch,
@@ -184,7 +181,6 @@ onDomReady(() => {
initCaptcha(); initCaptcha();
initUserAuthOauth2(); initUserAuthOauth2();
initUserAuthSAML();
initUserAuthWebAuthn(); initUserAuthWebAuthn();
initUserAuthWebAuthnRegister(); initUserAuthWebAuthnRegister();
initUserSettings(); initUserSettings();