mirror of
https://github.com/go-gitea/gitea.git
synced 2026-10-06 18:57:34 +02:00
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:
37 files changed
+69
-1440
No files matched your search
@@ -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
|
||||||
|
|||||||
Generated
-25
@@ -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`.
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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(®isteredApps); err != nil {
|
if err := db.GetEngine(ctx).In("client_id", builtinAllClientIDs).Find(®isteredApps); 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})
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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{}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
}
|
||||||
@@ -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,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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
|
||||||
@@ -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>
|
||||||
@@ -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
|
|
||||||
```
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -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();
|
||||||
|
|||||||
@@ -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
@@ -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();
|
||||||
|
|||||||
Reference in new issue
Block a user