From 532da5ed5ee3edb45d2ee63c6ab0fad53473691f Mon Sep 17 00:00:00 2001
From: silverwind
Date: Thu, 22 Feb 2024 22:21:43 +0100
Subject: [PATCH 02/79] Don't show third-party JS errors in production builds
(#29303)
So we don't get issues like
https://github.com/go-gitea/gitea/issues/29080 and
https://github.com/go-gitea/gitea/issues/29273 any more. Only active in
[production
builds](https://webpack.js.org/guides/production/#specify-the-mode), in
non-production the errors will still show.
---
web_src/js/bootstrap.js | 15 ++++++++++++---
1 file changed, 12 insertions(+), 3 deletions(-)
diff --git a/web_src/js/bootstrap.js b/web_src/js/bootstrap.js
index f8d0c0cac0..e46c91e5e6 100644
--- a/web_src/js/bootstrap.js
+++ b/web_src/js/bootstrap.js
@@ -29,17 +29,26 @@ export function showGlobalErrorMessage(msg) {
* @param {ErrorEvent} e
*/
function processWindowErrorEvent(e) {
+ const err = e.error ?? e.reason;
+ const assetBaseUrl = String(new URL(__webpack_public_path__, window.location.origin));
+
+ // error is likely from browser extension or inline script. Do not show these in production builds.
+ if (!err.stack?.includes(assetBaseUrl) && window.config?.runModeIsProd) return;
+
+ let message;
if (e.type === 'unhandledrejection') {
- showGlobalErrorMessage(`JavaScript promise rejection: ${e.reason}. Open browser console to see more details.`);
- return;
+ message = `JavaScript promise rejection: ${err.message}.`;
+ } else {
+ message = `JavaScript error: ${e.message} (${e.filename} @ ${e.lineno}:${e.colno}).`;
}
+
if (!e.error && e.lineno === 0 && e.colno === 0 && e.filename === '' && window.navigator.userAgent.includes('FxiOS/')) {
// At the moment, Firefox (iOS) (10x) has an engine bug. See https://github.com/go-gitea/gitea/issues/20240
// If a script inserts a newly created (and content changed) element into DOM, there will be a nonsense error event reporting: Script error: line 0, col 0.
return; // ignore such nonsense error event
}
- showGlobalErrorMessage(`JavaScript error: ${e.message} (${e.filename} @ ${e.lineno}:${e.colno}). Open browser console to see more details.`);
+ showGlobalErrorMessage(`${message} Open browser console to see more details.`);
}
function initGlobalErrorHandler() {
From c4b0cb4d0d527793296cf801e611f77666f86551 Mon Sep 17 00:00:00 2001
From: silverwind
Date: Fri, 23 Feb 2024 00:31:24 +0100
Subject: [PATCH 03/79] Upgrade to fabric 6 (#29334)
Upgrade fabric to latest v6 beta. It works for our use case, even
thought it does not fix the upstream issue
https://github.com/fabricjs/fabric.js/issues/9679 that
https://github.com/go-gitea/gitea/issues/29326 relates to.
---
Makefile | 2 +-
build/generate-images.js | 29 +++++++++++------------------
public/assets/img/favicon.svg | 2 +-
public/assets/img/logo.svg | 2 +-
4 files changed, 14 insertions(+), 21 deletions(-)
diff --git a/Makefile b/Makefile
index 7fa8193800..4ef02c6c54 100644
--- a/Makefile
+++ b/Makefile
@@ -969,7 +969,7 @@ generate-gitignore:
.PHONY: generate-images
generate-images: | node_modules
- npm install --no-save --no-package-lock fabric@5 imagemin-zopfli@7
+ npm install --no-save fabric@6.0.0-beta19 imagemin-zopfli@7
node build/generate-images.js $(TAGS)
.PHONY: generate-manpage
diff --git a/build/generate-images.js b/build/generate-images.js
index 09e3e068af..db31d19e2a 100755
--- a/build/generate-images.js
+++ b/build/generate-images.js
@@ -1,20 +1,13 @@
#!/usr/bin/env node
import imageminZopfli from 'imagemin-zopfli';
import {optimize} from 'svgo';
-import {fabric} from 'fabric';
+import {loadSVGFromString, Canvas, Rect, util} from 'fabric/node';
import {readFile, writeFile} from 'node:fs/promises';
+import {argv, exit} from 'node:process';
-function exit(err) {
+function doExit(err) {
if (err) console.error(err);
- process.exit(err ? 1 : 0);
-}
-
-function loadSvg(svg) {
- return new Promise((resolve) => {
- fabric.loadSVGFromString(svg, (objects, options) => {
- resolve({objects, options});
- });
- });
+ exit(err ? 1 : 0);
}
async function generate(svg, path, {size, bg}) {
@@ -35,14 +28,14 @@ async function generate(svg, path, {size, bg}) {
return;
}
- const {objects, options} = await loadSvg(svg);
- const canvas = new fabric.Canvas();
+ const {objects, options} = await loadSVGFromString(svg);
+ const canvas = new Canvas();
canvas.setDimensions({width: size, height: size});
const ctx = canvas.getContext('2d');
ctx.scale(options.width ? (size / options.width) : 1, options.height ? (size / options.height) : 1);
if (bg) {
- canvas.add(new fabric.Rect({
+ canvas.add(new Rect({
left: 0,
top: 0,
height: size * (1 / (size / options.height)),
@@ -51,7 +44,7 @@ async function generate(svg, path, {size, bg}) {
}));
}
- canvas.add(fabric.util.groupSVGElements(objects, options));
+ canvas.add(util.groupSVGElements(objects, options));
canvas.renderAll();
let png = Buffer.from([]);
@@ -64,7 +57,7 @@ async function generate(svg, path, {size, bg}) {
}
async function main() {
- const gitea = process.argv.slice(2).includes('gitea');
+ const gitea = argv.slice(2).includes('gitea');
const logoSvg = await readFile(new URL('../assets/logo.svg', import.meta.url), 'utf8');
const faviconSvg = await readFile(new URL('../assets/favicon.svg', import.meta.url), 'utf8');
@@ -80,7 +73,7 @@ async function main() {
}
try {
- exit(await main());
+ doExit(await main());
} catch (err) {
- exit(err);
+ doExit(err);
}
diff --git a/public/assets/img/favicon.svg b/public/assets/img/favicon.svg
index afeeacb77c..43291345df 100644
--- a/public/assets/img/favicon.svg
+++ b/public/assets/img/favicon.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/public/assets/img/logo.svg b/public/assets/img/logo.svg
index afeeacb77c..43291345df 100644
--- a/public/assets/img/logo.svg
+++ b/public/assets/img/logo.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
From 5bb8d1924d77c675467694de26697b876d709a17 Mon Sep 17 00:00:00 2001
From: techknowlogick
Date: Thu, 22 Feb 2024 19:08:17 -0500
Subject: [PATCH 04/79] Support SAML authentication (#25165)
Closes https://github.com/go-gitea/gitea/issues/5512
This PR adds basic SAML support
- Adds SAML 2.0 as an auth source
- Adds SAML configuration documentation
- Adds integration test:
- Use bare-bones SAML IdP to test protocol flow and test account is
linked successfully (only runs on Postgres by default)
- Adds documentation for configuring and running SAML integration test
locally
Future PRs:
- Support group mapping
- Support auto-registration (account linking)
Co-Authored-By: @jackHay22
---------
Co-authored-by: jackHay22
Co-authored-by: Lunny Xiao
Co-authored-by: KN4CK3R
Co-authored-by: wxiaoguang
Co-authored-by: Jason Song
Co-authored-by: morphelinho
Co-authored-by: Zettat123
Co-authored-by: Yarden Shoham
Co-authored-by: 6543 <6543@obermui.de>
Co-authored-by: silverwind
---
.github/workflows/pull-db-tests.yml | 8 +
assets/go-licenses.json | 25 +++
docs/content/usage/authentication.en-us.md | 69 ++++++
go.mod | 5 +
go.sum | 12 ++
models/auth/oauth2.go | 20 +-
models/auth/source.go | 38 ++++
options/locale/locale_en-US.ini | 14 ++
routers/init.go | 2 +
routers/web/admin/auths.go | 84 ++++++++
routers/web/auth/auth.go | 35 ++-
routers/web/auth/linkaccount.go | 45 ++--
routers/web/auth/oauth.go | 19 +-
routers/web/auth/openid.go | 5 +-
routers/web/auth/saml.go | 172 +++++++++++++++
routers/web/web.go | 5 +
.../auth/source/saml/assert_interface_test.go | 22 ++
services/auth/source/saml/init.go | 29 +++
services/auth/source/saml/name_id_format.go | 38 ++++
services/auth/source/saml/providers.go | 109 ++++++++++
services/auth/source/saml/source.go | 202 ++++++++++++++++++
.../auth/source/saml/source_authenticate.go | 16 ++
services/auth/source/saml/source_callout.go | 89 ++++++++
services/auth/source/saml/source_metadata.go | 32 +++
services/auth/source/saml/source_register.go | 23 ++
services/externalaccount/link.go | 11 +-
services/externalaccount/user.go | 12 +-
services/forms/auth_form.go | 15 +-
templates/admin/auth/edit.tmpl | 66 ++++++
templates/admin/auth/new.tmpl | 6 +
templates/admin/auth/source/saml.tmpl | 62 ++++++
templates/user/auth/signin_inner.tmpl | 17 ++
tests/integration/README.md | 17 ++
tests/integration/saml_test.go | 150 +++++++++++++
web_src/js/features/admin/common.js | 8 +-
web_src/js/features/user-auth.js | 21 ++
web_src/js/index.js | 6 +-
37 files changed, 1440 insertions(+), 69 deletions(-)
create mode 100644 routers/web/auth/saml.go
create mode 100644 services/auth/source/saml/assert_interface_test.go
create mode 100644 services/auth/source/saml/init.go
create mode 100644 services/auth/source/saml/name_id_format.go
create mode 100644 services/auth/source/saml/providers.go
create mode 100644 services/auth/source/saml/source.go
create mode 100644 services/auth/source/saml/source_authenticate.go
create mode 100644 services/auth/source/saml/source_callout.go
create mode 100644 services/auth/source/saml/source_metadata.go
create mode 100644 services/auth/source/saml/source_register.go
create mode 100644 templates/admin/auth/source/saml.tmpl
create mode 100644 tests/integration/saml_test.go
diff --git a/.github/workflows/pull-db-tests.yml b/.github/workflows/pull-db-tests.yml
index a3886bf618..8843c6d65e 100644
--- a/.github/workflows/pull-db-tests.yml
+++ b/.github/workflows/pull-db-tests.yml
@@ -37,6 +37,14 @@ jobs:
MINIO_ROOT_PASSWORD: 12345678
ports:
- "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:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
diff --git a/assets/go-licenses.json b/assets/go-licenses.json
index 2aa60780c4..ed722b0192 100644
--- a/assets/go-licenses.json
+++ b/assets/go-licenses.json
@@ -124,6 +124,11 @@
"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"
},
+ {
+ "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",
"path": "github.com/beorn7/perks/quantile/LICENSE",
@@ -639,6 +644,11 @@
"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"
},
+ {
+ "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 terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets \"{}\"\n replaced with your own identifying information. (Don't include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same \"printed page\" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright {yyyy} {name of copyright owner}\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n"
+ },
{
"name": "github.com/josharian/intern",
"path": "github.com/josharian/intern/license.md",
@@ -719,6 +729,11 @@
"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"
},
+ {
+ "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 this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets \"[]\"\n replaced with your own identifying information. (Don't include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same \"printed page\" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright [yyyy] [name of copyright owner]\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n"
+ },
{
"name": "github.com/mattn/go-colorable",
"path": "github.com/mattn/go-colorable/LICENSE",
@@ -904,6 +919,16 @@
"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"
},
+ {
+ "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 this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n"
+ },
+ {
+ "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 this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n"
+ },
{
"name": "github.com/russross/blackfriday/v2",
"path": "github.com/russross/blackfriday/v2/LICENSE.txt",
diff --git a/docs/content/usage/authentication.en-us.md b/docs/content/usage/authentication.en-us.md
index adc936dfbe..1838cfcc77 100644
--- a/docs/content/usage/authentication.en-us.md
+++ b/docs/content/usage/authentication.en-us.md
@@ -349,3 +349,72 @@ 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.
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`.
diff --git a/go.mod b/go.mod
index 7a752ec874..012a34612f 100644
--- a/go.mod
+++ b/go.mod
@@ -91,6 +91,8 @@ require (
github.com/quasoft/websspi v1.1.2
github.com/redis/go-redis/v9 v9.4.0
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/sassoftware/go-rpmutils v0.2.1-0.20240124161140-277b154961dd
github.com/sergi/go-diff v1.3.1
@@ -143,6 +145,7 @@ require (
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // 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/bits-and-blooms/bitset v1.13.0 // indirect
github.com/blevesearch/bleve_index_api v1.1.5 // indirect
@@ -216,6 +219,7 @@ require (
github.com/imdario/mergo v0.3.16 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // 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/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
@@ -225,6 +229,7 @@ require (
github.com/magiconair/properties v1.8.7 // indirect
github.com/mailru/easyjson v0.7.7 // 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-runewidth v0.0.15 // indirect
github.com/mholt/acmez v1.2.0 // indirect
diff --git a/go.sum b/go.sum
index b3b8ad8ce4..393e10cfa0 100644
--- a/go.sum
+++ b/go.sum
@@ -130,6 +130,8 @@ 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/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/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/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bits-and-blooms/bitset v1.1.10/go.mod h1:w0XsmFg8qg6cmpTtJ0z3pKgjTDBMMnI/+I2syrE6XBE=
@@ -566,6 +568,9 @@ github.com/jhillyerd/enmime v1.1.0 h1:ubaIzg68VY7CMCe2YbHe6nkRvU9vujixTkNz3EBvZO
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/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/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
@@ -634,6 +639,8 @@ 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/goth v1.78.0 h1:7VEIFDycJp9deyVv3YraGBPdD0ZYQW93Y3Aw1eVP3BY=
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/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
@@ -766,12 +773,17 @@ 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/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.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.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/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
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/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
diff --git a/models/auth/oauth2.go b/models/auth/oauth2.go
index 9d53fffc78..a252458d4e 100644
--- a/models/auth/oauth2.go
+++ b/models/auth/oauth2.go
@@ -8,6 +8,7 @@ import (
"crypto/sha256"
"encoding/base32"
"encoding/base64"
+ "encoding/gob"
"fmt"
"net"
"net/url"
@@ -81,6 +82,10 @@ func Init(ctx context.Context) error {
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
if err := db.GetEngine(ctx).In("client_id", builtinAllClientIDs).Find(®isteredApps); err != nil {
return err
@@ -605,21 +610,6 @@ func (err ErrOAuthApplicationNotFound) Unwrap() error {
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 {
deleteCond := builder.Select("id").From("oauth2_grant").Where(builder.Eq{"oauth2_grant.user_id": userID})
diff --git a/models/auth/source.go b/models/auth/source.go
index 1bdde8235c..bc564d35ba 100644
--- a/models/auth/source.go
+++ b/models/auth/source.go
@@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
+ "github.com/markbates/goth"
"xorm.io/builder"
"xorm.io/xorm"
"xorm.io/xorm/convert"
@@ -32,6 +33,7 @@ const (
DLDAP // 5
OAuth2 // 6
SSPI // 7
+ SAML // 8
)
// String returns the string name of the LoginType
@@ -52,6 +54,7 @@ var Names = map[Type]string{
PAM: "PAM",
OAuth2: "OAuth2",
SSPI: "SPNEGO with SSPI",
+ SAML: "SAML",
}
// Config represents login config as far as the db is concerned
@@ -121,6 +124,12 @@ type Source struct {
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
func (Source) TableName() string {
return "login_source"
@@ -180,6 +189,11 @@ func (source *Source) IsSSPI() bool {
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.
func (source *Source) HasTLS() bool {
hasTLSer, ok := source.Cfg.(HasTLSer)
@@ -392,3 +406,27 @@ func IsErrSourceInUse(err error) bool {
func (err ErrSourceInUse) Error() string {
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
+}
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 574e99e654..ae34d72e41 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -522,6 +522,9 @@ Content = Content
SSPISeparatorReplacement = Separator
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.`
alpha_dash_error = ` should contain only alphanumeric, dash ('-') and underscore ('_') characters.`
alpha_dash_dot_error = ` should contain only alphanumeric, dash ('-'), underscore ('_') and dot ('.') characters.`
@@ -3026,7 +3029,18 @@ 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_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.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.saml = Documentation can be found at https://docs.gitea.com/usage/authentication#saml
auths.tips.oauth2.general = OAuth2 Authentication
auths.tips.oauth2.general.tip = When registering a new OAuth2 authentication, the callback/redirect URL should be:
auths.tip.oauth2_provider = OAuth2 Provider
diff --git a/routers/init.go b/routers/init.go
index e0a7150ba3..9ae8c368a2 100644
--- a/routers/init.go
+++ b/routers/init.go
@@ -35,6 +35,7 @@ import (
actions_service "code.gitea.io/gitea/services/actions"
"code.gitea.io/gitea/services/auth"
"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/cron"
feed_service "code.gitea.io/gitea/services/feed"
@@ -138,6 +139,7 @@ func InitWebInstalled(ctx context.Context) {
log.Info("ORM engine initialization successful!")
mustInit(system.Init)
mustInitCtx(ctx, oauth2.Init)
+ mustInitCtx(ctx, saml.Init)
mustInit(release_service.Init)
diff --git a/routers/web/admin/auths.go b/routers/web/admin/auths.go
index 7fdd18dfae..187b569d39 100644
--- a/routers/web/admin/auths.go
+++ b/routers/web/admin/auths.go
@@ -1,9 +1,12 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
+// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package admin
import (
+ "crypto/tls"
+ "crypto/x509"
"errors"
"fmt"
"net/http"
@@ -25,6 +28,7 @@ import (
"code.gitea.io/gitea/services/auth/source/ldap"
"code.gitea.io/gitea/services/auth/source/oauth2"
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/sspi"
"code.gitea.io/gitea/services/forms"
@@ -71,6 +75,7 @@ var (
{auth.SMTP.String(), auth.SMTP},
{auth.OAuth2.String(), auth.OAuth2},
{auth.SSPI.String(), auth.SSPI},
+ {auth.SAML.String(), auth.SAML},
}
if pam.Supported {
items = append(items, dropdownItem{auth.Names[auth.PAM], auth.PAM})
@@ -83,6 +88,16 @@ var (
{ldap.SecurityProtocolNames[ldap.SecurityProtocolLDAPS], ldap.SecurityProtocolLDAPS},
{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
@@ -98,6 +113,8 @@ func NewAuthSource(ctx *context.Context) {
ctx.Data["is_sync_enabled"] = true
ctx.Data["AuthSources"] = authSources
ctx.Data["SecurityProtocols"] = securityProtocols
+ ctx.Data["CurrentNameIDFormat"] = saml.NameIDFormatNames[saml.SAML20Persistent]
+ ctx.Data["NameIDFormats"] = nameIDFormats
ctx.Data["SMTPAuths"] = smtp.Authenticators
oauth2providers := oauth2.GetSupportedOAuth2Providers()
ctx.Data["OAuth2Providers"] = oauth2providers
@@ -231,6 +248,52 @@ func parseSSPIConfig(ctx *context.Context, form forms.AuthenticationForm) (*sspi
}, 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
func NewAuthSourcePost(ctx *context.Context) {
form := *web.GetForm(ctx).(*forms.AuthenticationForm)
@@ -244,6 +307,8 @@ func NewAuthSourcePost(ctx *context.Context) {
ctx.Data["SMTPAuths"] = smtp.Authenticators
oauth2providers := oauth2.GetSupportedOAuth2Providers()
ctx.Data["OAuth2Providers"] = oauth2providers
+ ctx.Data["CurrentNameIDFormat"] = saml.NameIDFormatNames[saml.NameIDFormat(form.NameIDFormat)]
+ ctx.Data["NameIDFormats"] = nameIDFormats
ctx.Data["SSPIAutoCreateUsers"] = true
ctx.Data["SSPIAutoActivateUsers"] = true
@@ -290,6 +355,13 @@ func NewAuthSourcePost(ctx *context.Context) {
ctx.RenderWithErr(ctx.Tr("admin.auths.login_source_of_type_exist"), tplAuthNew, form)
return
}
+ case auth.SAML:
+ var err error
+ config, err = parseSAMLConfig(ctx, form)
+ if err != nil {
+ ctx.RenderWithErr(err.Error(), tplAuthNew, form)
+ return
+ }
default:
ctx.Error(http.StatusBadRequest)
return
@@ -336,6 +408,7 @@ func EditAuthSource(ctx *context.Context) {
ctx.Data["SMTPAuths"] = smtp.Authenticators
oauth2providers := oauth2.GetSupportedOAuth2Providers()
ctx.Data["OAuth2Providers"] = oauth2providers
+ ctx.Data["NameIDFormats"] = nameIDFormats
source, err := auth.GetSourceByID(ctx, ctx.ParamsInt64(":authid"))
if err != nil {
@@ -344,6 +417,9 @@ func EditAuthSource(ctx *context.Context) {
}
ctx.Data["Source"] = source
ctx.Data["HasTLS"] = source.HasTLS()
+ if source.IsSAML() {
+ ctx.Data["CurrentNameIDFormat"] = saml.NameIDFormatNames[source.Cfg.(*saml.Source).NameIDFormat]
+ }
if source.IsOAuth2() {
type Named interface {
@@ -378,6 +454,8 @@ func EditAuthSourcePost(ctx *context.Context) {
}
ctx.Data["Source"] = source
ctx.Data["HasTLS"] = source.HasTLS()
+ ctx.Data["CurrentNameIDFormat"] = saml.NameIDFormatNames[saml.SAML20Persistent]
+ ctx.Data["NameIDFormats"] = nameIDFormats
if ctx.HasError() {
ctx.HTML(http.StatusOK, tplAuthEdit)
@@ -412,6 +490,12 @@ func EditAuthSourcePost(ctx *context.Context) {
ctx.RenderWithErr(err.Error(), tplAuthEdit, form)
return
}
+ case auth.SAML:
+ config, err = parseSAMLConfig(ctx, form)
+ if err != nil {
+ ctx.RenderWithErr(err.Error(), tplAuthEdit, form)
+ return
+ }
default:
ctx.Error(http.StatusBadRequest)
return
diff --git a/routers/web/auth/auth.go b/routers/web/auth/auth.go
index 3de1f3373d..f5955ec5ff 100644
--- a/routers/web/auth/auth.go
+++ b/routers/web/auth/auth.go
@@ -28,6 +28,7 @@ import (
"code.gitea.io/gitea/routers/utils"
auth_service "code.gitea.io/gitea/services/auth"
"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/forms"
"code.gitea.io/gitea/services/mailer"
@@ -170,6 +171,14 @@ func SignIn(ctx *context.Context) {
return
}
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["SignInLink"] = setting.AppSubURL + "/user/login"
ctx.Data["PageIsSignIn"] = true
@@ -193,6 +202,14 @@ func SignInPost(ctx *context.Context) {
return
}
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["SignInLink"] = setting.AppSubURL + "/user/login"
ctx.Data["PageIsSignIn"] = true
@@ -504,7 +521,7 @@ func SignUpPost(ctx *context.Context) {
Passwd: form.Password,
}
- if !createAndHandleCreatedUser(ctx, tplSignUp, form, u, nil, nil, false) {
+ if !createAndHandleCreatedUser(ctx, tplSignUp, form, u, nil, nil, false, auth.NoType) {
// error already handled
return
}
@@ -515,16 +532,16 @@ func SignUpPost(ctx *context.Context) {
// createAndHandleCreatedUser calls createUserInContext and
// 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) bool {
- if !createUserInContext(ctx, tpl, form, u, overwrites, gothUser, allowLink) {
+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 {
+ if !createUserInContext(ctx, tpl, form, u, overwrites, gothUser, allowLink, authType) {
return false
}
- return handleUserCreated(ctx, u, gothUser)
+ return handleUserCreated(ctx, u, gothUser, authType)
}
// createUserInContext creates a user and handles errors within a given context.
// 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) (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, authType auth.Type) (ok bool) {
if err := user_model.CreateUser(ctx, u, overwrites); err != nil {
if allowLink && (user_model.IsErrUserAlreadyExist(err) || user_model.IsErrEmailAlreadyUsed(err)) {
if setting.OAuth2Client.AccountLinking == setting.OAuth2AccountLinkingAuto {
@@ -541,10 +558,10 @@ func createUserInContext(ctx *context.Context, tpl base.TplName, form any, u *us
}
// TODO: probably we should respect 'remember' user's choice...
- linkAccount(ctx, user, *gothUser, true)
+ linkAccount(ctx, user, *gothUser, true, authType)
return false // user is already created here, all redirects are handled
} else if setting.OAuth2Client.AccountLinking == setting.OAuth2AccountLinkingLogin {
- showLinkingLogin(ctx, *gothUser)
+ showLinkingLogin(ctx, *gothUser, authType)
return false // user will be created only after linking login
}
}
@@ -590,7 +607,7 @@ func createUserInContext(ctx *context.Context, tpl base.TplName, form any, u *us
// handleUserCreated does additional steps after a new user is created.
// It auto-sets admin for the only user, updates the optional external user and
// sends a confirmation email if required.
-func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth.User) (ok bool) {
+func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth.User, authType auth.Type) (ok bool) {
// Auto-set admin for the only user.
if user_model.CountUsers(ctx, nil) == 1 {
opts := &user_service.UpdateOptions{
@@ -606,7 +623,7 @@ func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth.
// update external user information
if gothUser != nil {
- if err := externalaccount.UpdateExternalUser(ctx, u, *gothUser); err != nil {
+ if err := externalaccount.UpdateExternalUser(ctx, u, *gothUser, authType); err != nil {
if !errors.Is(err, util.ErrNotExist) {
log.Error("UpdateExternalUser failed: %v", err)
}
diff --git a/routers/web/auth/linkaccount.go b/routers/web/auth/linkaccount.go
index 1d94e52fe3..c62ae84083 100644
--- a/routers/web/auth/linkaccount.go
+++ b/routers/web/auth/linkaccount.go
@@ -48,13 +48,13 @@ func LinkAccount(ctx *context.Context) {
ctx.Data["SignInLink"] = setting.AppSubURL + "/user/link_account_signin"
ctx.Data["SignUpLink"] = setting.AppSubURL + "/user/link_account_signup"
- gothUser := ctx.Session.Get("linkAccountGothUser")
- if gothUser == nil {
+ externalLinkUser := ctx.Session.Get("linkAccountUser")
+ if externalLinkUser == nil {
ctx.ServerError("UserSignIn", errors.New("not in LinkAccount session"))
return
}
- gu, _ := gothUser.(goth.User)
+ gu := externalLinkUser.(auth.LinkAccountUser).GothUser
uname, err := getUserName(&gu)
if err != nil {
ctx.ServerError("UserSignIn", err)
@@ -135,12 +135,14 @@ func LinkAccountPostSignIn(ctx *context.Context) {
ctx.Data["SignInLink"] = setting.AppSubURL + "/user/link_account_signin"
ctx.Data["SignUpLink"] = setting.AppSubURL + "/user/link_account_signup"
- gothUser := ctx.Session.Get("linkAccountGothUser")
- if gothUser == nil {
+ externalLinkUserInterface := ctx.Session.Get("linkAccountUser")
+ if externalLinkUserInterface == nil {
ctx.ServerError("UserSignIn", errors.New("not in LinkAccount session"))
return
}
+ externalLinkUser := externalLinkUserInterface.(auth.LinkAccountUser)
+
if ctx.HasError() {
ctx.HTML(http.StatusOK, tplLinkAccount)
return
@@ -152,10 +154,10 @@ func LinkAccountPostSignIn(ctx *context.Context) {
return
}
- linkAccount(ctx, u, gothUser.(goth.User), signInForm.Remember)
+ linkAccount(ctx, u, externalLinkUser.GothUser, signInForm.Remember, externalLinkUser.Type)
}
-func linkAccount(ctx *context.Context, u *user_model.User, gothUser goth.User, remember bool) {
+func linkAccount(ctx *context.Context, u *user_model.User, gothUser goth.User, remember bool, authType auth.Type) {
updateAvatarIfNeed(ctx, gothUser.AvatarURL, u)
// If this user is enrolled in 2FA, we can't sign the user in just yet.
@@ -168,7 +170,7 @@ func linkAccount(ctx *context.Context, u *user_model.User, gothUser goth.User, r
return
}
- err = externalaccount.LinkAccountToUser(ctx, u, gothUser)
+ err = externalaccount.LinkAccountToUser(ctx, u, gothUser, authType)
if err != nil {
ctx.ServerError("UserLinkAccount", err)
return
@@ -222,14 +224,14 @@ func LinkAccountPostRegister(ctx *context.Context) {
ctx.Data["SignInLink"] = setting.AppSubURL + "/user/link_account_signin"
ctx.Data["SignUpLink"] = setting.AppSubURL + "/user/link_account_signup"
- gothUserInterface := ctx.Session.Get("linkAccountGothUser")
- if gothUserInterface == nil {
+ externalLinkUser := ctx.Session.Get("linkAccountUser")
+ if externalLinkUser == nil {
ctx.ServerError("UserSignUp", errors.New("not in LinkAccount session"))
return
}
- gothUser, ok := gothUserInterface.(goth.User)
+ linkUser, ok := externalLinkUser.(auth.LinkAccountUser)
if !ok {
- ctx.ServerError("UserSignUp", fmt.Errorf("session linkAccountGothUser type is %t but not goth.User", gothUserInterface))
+ ctx.ServerError("UserSignUp", fmt.Errorf("session linkAccountUser type is %t but not goth.User", externalLinkUser))
return
}
@@ -275,7 +277,7 @@ func LinkAccountPostRegister(ctx *context.Context) {
}
}
- authSource, err := auth.GetActiveOAuth2SourceByName(ctx, gothUser.Provider)
+ authSource, err := auth.GetActiveAuthSourceByName(ctx, linkUser.GothUser.Provider, linkUser.Type)
if err != nil {
ctx.ServerError("CreateUser", err)
return
@@ -285,21 +287,24 @@ func LinkAccountPostRegister(ctx *context.Context) {
Name: form.UserName,
Email: form.Email,
Passwd: form.Password,
- LoginType: auth.OAuth2,
+ LoginType: authSource.Type,
LoginSource: authSource.ID,
- LoginName: gothUser.UserID,
+ LoginName: linkUser.GothUser.UserID,
}
- if !createAndHandleCreatedUser(ctx, tplLinkAccount, form, u, nil, &gothUser, false) {
+ if !createAndHandleCreatedUser(ctx, tplLinkAccount, form, u, nil, &linkUser.GothUser, false, linkUser.Type) {
// error already handled
return
}
- source := authSource.Cfg.(*oauth2.Source)
- if err := syncGroupsToTeams(ctx, source, &gothUser, u); err != nil {
- ctx.ServerError("SyncGroupsToTeams", err)
- return
+ if linkUser.Type == auth.OAuth2 {
+ source := authSource.Cfg.(*oauth2.Source)
+ if err := syncGroupsToTeams(ctx, source, &linkUser.GothUser, u); err != nil {
+ ctx.ServerError("SyncGroupsToTeams", err)
+ return
+ }
}
+ // TODO we will support some form of group mapping for SAML
handleSignIn(ctx, u, false)
}
diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go
index ee0770ef37..d00644dd5f 100644
--- a/routers/web/auth/oauth.go
+++ b/routers/web/auth/oauth.go
@@ -841,7 +841,7 @@ func handleAuthorizeError(ctx *context.Context, authErr AuthorizeError, redirect
func SignInOAuth(ctx *context.Context) {
provider := ctx.Params(":provider")
- authSource, err := auth.GetActiveOAuth2SourceByName(ctx, provider)
+ authSource, err := auth.GetActiveAuthSourceByName(ctx, provider, auth.OAuth2)
if err != nil {
ctx.ServerError("SignIn", err)
return
@@ -892,7 +892,7 @@ func SignInOAuthCallback(ctx *context.Context) {
}
// first look if the provider is still active
- authSource, err := auth.GetActiveOAuth2SourceByName(ctx, provider)
+ authSource, err := auth.GetActiveAuthSourceByName(ctx, provider, auth.OAuth2)
if err != nil {
ctx.ServerError("SignIn", err)
return
@@ -935,7 +935,7 @@ func SignInOAuthCallback(ctx *context.Context) {
if u == nil {
if ctx.Doer != nil {
// attach user to already logged in user
- err = externalaccount.LinkAccountToUser(ctx, ctx.Doer, gothUser)
+ err = externalaccount.LinkAccountToUser(ctx, ctx.Doer, gothUser, auth.OAuth2)
if err != nil {
ctx.ServerError("UserLinkAccount", err)
return
@@ -988,7 +988,7 @@ func SignInOAuthCallback(ctx *context.Context) {
u.IsAdmin = isAdmin.ValueOrDefault(false)
u.IsRestricted = isRestricted.ValueOrDefault(false)
- if !createAndHandleCreatedUser(ctx, base.TplName(""), nil, u, overwriteDefault, &gothUser, setting.OAuth2Client.AccountLinking != setting.OAuth2AccountLinkingDisabled) {
+ if !createAndHandleCreatedUser(ctx, base.TplName(""), nil, u, overwriteDefault, &gothUser, setting.OAuth2Client.AccountLinking != setting.OAuth2AccountLinkingDisabled, auth.OAuth2) {
// error already handled
return
}
@@ -999,7 +999,7 @@ func SignInOAuthCallback(ctx *context.Context) {
}
} else {
// no existing user is found, request attach or new account
- showLinkingLogin(ctx, gothUser)
+ showLinkingLogin(ctx, gothUser, auth.OAuth2)
return
}
}
@@ -1063,9 +1063,12 @@ func getUserAdminAndRestrictedFromGroupClaims(source *oauth2.Source, gothUser *g
return isAdmin, isRestricted
}
-func showLinkingLogin(ctx *context.Context, gothUser goth.User) {
+func showLinkingLogin(ctx *context.Context, gothUser goth.User, authType auth.Type) {
if err := updateSession(ctx, nil, map[string]any{
- "linkAccountGothUser": gothUser,
+ "linkAccountUser": auth.LinkAccountUser{
+ Type: authType,
+ GothUser: gothUser,
+ },
}); err != nil {
ctx.ServerError("updateSession", err)
return
@@ -1144,7 +1147,7 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model
}
// update external user information
- if err := externalaccount.UpdateExternalUser(ctx, u, gothUser); err != nil {
+ if err := externalaccount.UpdateExternalUser(ctx, u, gothUser, auth.OAuth2); err != nil {
if !errors.Is(err, util.ErrNotExist) {
log.Error("UpdateExternalUser failed: %v", err)
}
diff --git a/routers/web/auth/openid.go b/routers/web/auth/openid.go
index 29ef772b1c..bf377b4496 100644
--- a/routers/web/auth/openid.go
+++ b/routers/web/auth/openid.go
@@ -8,6 +8,7 @@ import (
"net/http"
"net/url"
+ auth_model "code.gitea.io/gitea/models/auth"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/auth/openid"
"code.gitea.io/gitea/modules/base"
@@ -363,7 +364,7 @@ func RegisterOpenIDPost(ctx *context.Context) {
Email: form.Email,
Passwd: password,
}
- if !createUserInContext(ctx, tplSignUpOID, form, u, nil, nil, false) {
+ if !createUserInContext(ctx, tplSignUpOID, form, u, nil, nil, false, auth_model.NoType) {
// error already handled
return
}
@@ -379,7 +380,7 @@ func RegisterOpenIDPost(ctx *context.Context) {
return
}
- if !handleUserCreated(ctx, u, nil) {
+ if !handleUserCreated(ctx, u, nil, auth_model.NoType) {
// error already handled
return
}
diff --git a/routers/web/auth/saml.go b/routers/web/auth/saml.go
new file mode 100644
index 0000000000..29d689d2e9
--- /dev/null
+++ b/routers/web/auth/saml.go
@@ -0,0 +1,172 @@
+// 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)
+ }
+}
diff --git a/routers/web/web.go b/routers/web/web.go
index 864164972e..77c8319f06 100644
--- a/routers/web/web.go
+++ b/routers/web/web.go
@@ -667,6 +667,11 @@ func registerRoutes(m *web.Route) {
m.Get("/{provider}", auth.SignInOAuth)
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 *****
diff --git a/services/auth/source/saml/assert_interface_test.go b/services/auth/source/saml/assert_interface_test.go
new file mode 100644
index 0000000000..2ca7057b8a
--- /dev/null
+++ b/services/auth/source/saml/assert_interface_test.go
@@ -0,0 +1,22 @@
+// 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{}
diff --git a/services/auth/source/saml/init.go b/services/auth/source/saml/init.go
new file mode 100644
index 0000000000..f1d6d9fa4b
--- /dev/null
+++ b/services/auth/source/saml/init.go
@@ -0,0 +1,29 @@
+// 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
+}
diff --git a/services/auth/source/saml/name_id_format.go b/services/auth/source/saml/name_id_format.go
new file mode 100644
index 0000000000..1ddf047729
--- /dev/null
+++ b/services/auth/source/saml/name_id_format.go
@@ -0,0 +1,38 @@
+// 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)
+}
diff --git a/services/auth/source/saml/providers.go b/services/auth/source/saml/providers.go
new file mode 100644
index 0000000000..d0b36ff44d
--- /dev/null
+++ b/services/auth/source/saml/providers.go
@@ -0,0 +1,109 @@
+// 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(``,
+ 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
+}
diff --git a/services/auth/source/saml/source.go b/services/auth/source/saml/source.go
new file mode 100644
index 0000000000..52388646b5
--- /dev/null
+++ b/services/auth/source/saml/source.go
@@ -0,0 +1,202 @@
+// 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 `` or ``. 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 ".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{})
+}
diff --git a/services/auth/source/saml/source_authenticate.go b/services/auth/source/saml/source_authenticate.go
new file mode 100644
index 0000000000..d118917f87
--- /dev/null
+++ b/services/auth/source/saml/source_authenticate.go
@@ -0,0 +1,16 @@
+// 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)
+}
diff --git a/services/auth/source/saml/source_callout.go b/services/auth/source/saml/source_callout.go
new file mode 100644
index 0000000000..5366f8a527
--- /dev/null
+++ b/services/auth/source/saml/source_callout.go
@@ -0,0 +1,89 @@
+// 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
+}
diff --git a/services/auth/source/saml/source_metadata.go b/services/auth/source/saml/source_metadata.go
new file mode 100644
index 0000000000..9fb8c758e3
--- /dev/null
+++ b/services/auth/source/saml/source_metadata.go
@@ -0,0 +1,32 @@
+// 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
+}
diff --git a/services/auth/source/saml/source_register.go b/services/auth/source/saml/source_register.go
new file mode 100644
index 0000000000..93eaaa88b6
--- /dev/null
+++ b/services/auth/source/saml/source_register.go
@@ -0,0 +1,23 @@
+// 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
+}
diff --git a/services/externalaccount/link.go b/services/externalaccount/link.go
index d6e2ea7e94..1f4c6728b8 100644
--- a/services/externalaccount/link.go
+++ b/services/externalaccount/link.go
@@ -7,9 +7,8 @@ import (
"context"
"fmt"
+ "code.gitea.io/gitea/models/auth"
user_model "code.gitea.io/gitea/models/user"
-
- "github.com/markbates/goth"
)
// Store represents a thing that stores things
@@ -21,10 +20,12 @@ type Store interface {
// LinkAccountFromStore links the provided user with a stored external user
func LinkAccountFromStore(ctx context.Context, store Store, user *user_model.User) error {
- gothUser := store.Get("linkAccountGothUser")
- if gothUser == nil {
+ externalLinkUserInterface := store.Get("linkAccountUser")
+ if externalLinkUserInterface == nil {
return fmt.Errorf("not in LinkAccount session")
}
- return LinkAccountToUser(ctx, user, gothUser.(goth.User))
+ externalLinkUser := externalLinkUserInterface.(auth.LinkAccountUser)
+
+ return LinkAccountToUser(ctx, user, externalLinkUser.GothUser, externalLinkUser.Type)
}
diff --git a/services/externalaccount/user.go b/services/externalaccount/user.go
index e2de41da18..fa85a65669 100644
--- a/services/externalaccount/user.go
+++ b/services/externalaccount/user.go
@@ -16,8 +16,8 @@ import (
"github.com/markbates/goth"
)
-func toExternalLoginUser(ctx context.Context, user *user_model.User, gothUser goth.User) (*user_model.ExternalLoginUser, error) {
- authSource, err := auth.GetActiveOAuth2SourceByName(ctx, gothUser.Provider)
+func toExternalLoginUser(ctx context.Context, user *user_model.User, gothUser goth.User, authType auth.Type) (*user_model.ExternalLoginUser, error) {
+ authSource, err := auth.GetActiveAuthSourceByName(ctx, gothUser.Provider, authType)
if err != nil {
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
-func LinkAccountToUser(ctx context.Context, user *user_model.User, gothUser goth.User) error {
- externalLoginUser, err := toExternalLoginUser(ctx, user, gothUser)
+func LinkAccountToUser(ctx context.Context, user *user_model.User, gothUser goth.User, authType auth.Type) error {
+ externalLoginUser, err := toExternalLoginUser(ctx, user, gothUser, authType)
if err != nil {
return err
}
@@ -71,8 +71,8 @@ func LinkAccountToUser(ctx context.Context, user *user_model.User, gothUser goth
}
// UpdateExternalUser updates external user's information
-func UpdateExternalUser(ctx context.Context, user *user_model.User, gothUser goth.User) error {
- externalLoginUser, err := toExternalLoginUser(ctx, user, gothUser)
+func UpdateExternalUser(ctx context.Context, user *user_model.User, gothUser goth.User, authType auth.Type) error {
+ externalLoginUser, err := toExternalLoginUser(ctx, user, gothUser, authType)
if err != nil {
return err
}
diff --git a/services/forms/auth_form.go b/services/forms/auth_form.go
index 25acbbb99e..85be38b403 100644
--- a/services/forms/auth_form.go
+++ b/services/forms/auth_form.go
@@ -1,3 +1,4 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
// Copyright 2014 The Gogs Authors. All rights reserved.
// SPDX-License-Identifier: MIT
@@ -15,7 +16,7 @@ import (
// AuthenticationForm form for authentication
type AuthenticationForm struct {
ID int64
- Type int `binding:"Range(2,7)"`
+ Type int `binding:"Range(2,9)"`
Name string `binding:"Required;MaxSize(30)"`
Host string
Port int
@@ -82,6 +83,18 @@ type AuthenticationForm struct {
SSPIDefaultLanguage string
GroupTeamMap string `binding:"ValidGroupTeamMap"`
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
diff --git a/templates/admin/auth/edit.tmpl b/templates/admin/auth/edit.tmpl
index 25abefae00..2182d011e9 100644
--- a/templates/admin/auth/edit.tmpl
+++ b/templates/admin/auth/edit.tmpl
@@ -367,6 +367,69 @@
{{end}}
+
+ {{if .Source.IsSAML}}
+ {{$cfg:=.Source.Cfg}}
+
+ {{ isLoading ? locale.loadingTitle : errorText ? locale.loadingTitleFailed: `Code frequency over the history of ${repoLink.slice(1)}` }}
+
+
+
+
+
+ {{ locale.loadingInfo }}
+
+
+
+ {{ errorText }}
+
+
+
+
+
+
+
diff --git a/web_src/js/components/RepoContributors.vue b/web_src/js/components/RepoContributors.vue
index fa1545b3df..84fdcae1f6 100644
--- a/web_src/js/components/RepoContributors.vue
+++ b/web_src/js/components/RepoContributors.vue
@@ -3,10 +3,7 @@ import {SvgIcon} from '../svg.js';
import {
Chart,
Title,
- Tooltip,
- Legend,
BarElement,
- CategoryScale,
LinearScale,
TimeScale,
PointElement,
@@ -21,27 +18,13 @@ import {
firstStartDateAfterDate,
fillEmptyStartDaysWithZeroes,
} from '../utils/time.js';
+import {chartJsColors} from '../utils/color.js';
+import {sleep} from '../utils.js';
import 'chartjs-adapter-dayjs-4/dist/chartjs-adapter-dayjs-4.esm';
import $ from 'jquery';
const {pageData} = window.config;
-const colors = {
- text: '--color-text',
- border: '--color-secondary-alpha-60',
- commits: '--color-primary-alpha-60',
- additions: '--color-green',
- deletions: '--color-red',
- title: '--color-secondary-dark-4',
-};
-
-const styles = window.getComputedStyle(document.documentElement);
-const getColor = (name) => styles.getPropertyValue(name).trim();
-
-for (const [key, value] of Object.entries(colors)) {
- colors[key] = getColor(value);
-}
-
const customEventListener = {
id: 'customEventListener',
afterEvent: (chart, args, opts) => {
@@ -54,17 +37,14 @@ const customEventListener = {
}
};
-Chart.defaults.color = colors.text;
-Chart.defaults.borderColor = colors.border;
+Chart.defaults.color = chartJsColors.text;
+Chart.defaults.borderColor = chartJsColors.border;
Chart.register(
TimeScale,
- CategoryScale,
LinearScale,
BarElement,
Title,
- Tooltip,
- Legend,
PointElement,
LineElement,
Filler,
@@ -122,7 +102,7 @@ export default {
do {
response = await GET(`${this.repoLink}/activity/contributors/data`);
if (response.status === 202) {
- await new Promise((resolve) => setTimeout(resolve, 1000)); // wait for 1 second before retrying
+ await sleep(1000); // wait for 1 second before retrying
}
} while (response.status === 202);
if (response.ok) {
@@ -222,7 +202,7 @@ export default {
pointRadius: 0,
pointHitRadius: 0,
fill: 'start',
- backgroundColor: colors[this.type],
+ backgroundColor: chartJsColors[this.type],
borderWidth: 0,
tension: 0.3,
},
@@ -254,7 +234,6 @@ export default {
title: {
display: type === 'main',
text: 'drag: zoom, shift+drag: pan, double click: reset zoom',
- color: colors.title,
position: 'top',
align: 'center',
},
@@ -262,9 +241,6 @@ export default {
chartType: type,
instance: this,
},
- legend: {
- display: false,
- },
zoom: {
pan: {
enabled: true,
diff --git a/web_src/js/features/code-frequency.js b/web_src/js/features/code-frequency.js
new file mode 100644
index 0000000000..103d82f6e3
--- /dev/null
+++ b/web_src/js/features/code-frequency.js
@@ -0,0 +1,21 @@
+import {createApp} from 'vue';
+
+export async function initRepoCodeFrequency() {
+ const el = document.getElementById('repo-code-frequency-chart');
+ if (!el) return;
+
+ const {default: RepoCodeFrequency} = await import(/* webpackChunkName: "code-frequency-graph" */'../components/RepoCodeFrequency.vue');
+ try {
+ const View = createApp(RepoCodeFrequency, {
+ locale: {
+ loadingTitle: el.getAttribute('data-locale-loading-title'),
+ loadingTitleFailed: el.getAttribute('data-locale-loading-title-failed'),
+ loadingInfo: el.getAttribute('data-locale-loading-info'),
+ }
+ });
+ View.mount(el);
+ } catch (err) {
+ console.error('RepoCodeFrequency failed to load', err);
+ el.textContent = el.getAttribute('data-locale-component-failed-to-load');
+ }
+}
diff --git a/web_src/js/index.js b/web_src/js/index.js
index ddd435f05e..876e4291ee 100644
--- a/web_src/js/index.js
+++ b/web_src/js/index.js
@@ -87,6 +87,7 @@ import {onDomReady} from './utils/dom.js';
import {initRepoIssueList} from './features/repo-issue-list.js';
import {initCommonIssueListQuickGoto} from './features/common-issue-list.js';
import {initRepoContributors} from './features/contributors.js';
+import {initRepoCodeFrequency} from './features/code-frequency.js';
import {initRepoDiffCommitBranchesAndTags} from './features/repo-diff-commit.js';
import {initDirAuto} from './modules/dirauto.js';
@@ -177,6 +178,7 @@ onDomReady(() => {
initRepository();
initRepositoryActionView();
initRepoContributors();
+ initRepoCodeFrequency();
initCommitStatuses();
initCaptcha();
diff --git a/web_src/js/utils.js b/web_src/js/utils.js
index c82e42d349..3a2694335f 100644
--- a/web_src/js/utils.js
+++ b/web_src/js/utils.js
@@ -139,3 +139,5 @@ export function parseDom(text, contentType) {
export function serializeXml(node) {
return xmlSerializer.serializeToString(node);
}
+
+export const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
diff --git a/web_src/js/utils/color.js b/web_src/js/utils/color.js
index 5d9c4ca45d..0ba6af49ee 100644
--- a/web_src/js/utils/color.js
+++ b/web_src/js/utils/color.js
@@ -19,3 +19,17 @@ function getLuminance(r, g, b) {
export function useLightTextOnBackground(r, g, b) {
return getLuminance(r, g, b) < 0.453;
}
+
+function resolveColors(obj) {
+ const styles = window.getComputedStyle(document.documentElement);
+ const getColor = (name) => styles.getPropertyValue(name).trim();
+ return Object.fromEntries(Object.entries(obj).map(([key, value]) => [key, getColor(value)]));
+}
+
+export const chartJsColors = resolveColors({
+ text: '--color-text',
+ border: '--color-secondary-alpha-60',
+ commits: '--color-primary-alpha-60',
+ additions: '--color-green',
+ deletions: '--color-red',
+});
From 4ba642d07d50d7eb42ae33cd6f1f7f2c82c02a40 Mon Sep 17 00:00:00 2001
From: 6543 <6543@obermui.de>
Date: Sat, 24 Feb 2024 05:18:49 +0100
Subject: [PATCH 18/79] Revert "Support SAML authentication (#25165)" (#29358)
This reverts #25165 (5bb8d1924d77c675467694de26697b876d709a17), 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
---
.github/workflows/pull-db-tests.yml | 8 -
assets/go-licenses.json | 25 ---
docs/content/usage/authentication.en-us.md | 69 ------
go.mod | 5 -
go.sum | 12 --
models/auth/oauth2.go | 20 +-
models/auth/source.go | 38 ----
options/locale/locale_en-US.ini | 14 --
routers/init.go | 2 -
routers/web/admin/auths.go | 84 --------
routers/web/auth/auth.go | 35 +--
routers/web/auth/linkaccount.go | 45 ++--
routers/web/auth/oauth.go | 19 +-
routers/web/auth/openid.go | 5 +-
routers/web/auth/saml.go | 172 ---------------
routers/web/web.go | 5 -
.../auth/source/saml/assert_interface_test.go | 22 --
services/auth/source/saml/init.go | 29 ---
services/auth/source/saml/name_id_format.go | 38 ----
services/auth/source/saml/providers.go | 109 ----------
services/auth/source/saml/source.go | 202 ------------------
.../auth/source/saml/source_authenticate.go | 16 --
services/auth/source/saml/source_callout.go | 89 --------
services/auth/source/saml/source_metadata.go | 32 ---
services/auth/source/saml/source_register.go | 23 --
services/externalaccount/link.go | 11 +-
services/externalaccount/user.go | 12 +-
services/forms/auth_form.go | 15 +-
templates/admin/auth/edit.tmpl | 66 ------
templates/admin/auth/new.tmpl | 6 -
templates/admin/auth/source/saml.tmpl | 62 ------
templates/user/auth/signin_inner.tmpl | 17 --
tests/integration/README.md | 17 --
tests/integration/saml_test.go | 150 -------------
web_src/js/features/admin/common.js | 8 +-
web_src/js/features/user-auth.js | 21 --
web_src/js/index.js | 6 +-
37 files changed, 69 insertions(+), 1440 deletions(-)
delete mode 100644 routers/web/auth/saml.go
delete mode 100644 services/auth/source/saml/assert_interface_test.go
delete mode 100644 services/auth/source/saml/init.go
delete mode 100644 services/auth/source/saml/name_id_format.go
delete mode 100644 services/auth/source/saml/providers.go
delete mode 100644 services/auth/source/saml/source.go
delete mode 100644 services/auth/source/saml/source_authenticate.go
delete mode 100644 services/auth/source/saml/source_callout.go
delete mode 100644 services/auth/source/saml/source_metadata.go
delete mode 100644 services/auth/source/saml/source_register.go
delete mode 100644 templates/admin/auth/source/saml.tmpl
delete mode 100644 tests/integration/saml_test.go
diff --git a/.github/workflows/pull-db-tests.yml b/.github/workflows/pull-db-tests.yml
index 8843c6d65e..a3886bf618 100644
--- a/.github/workflows/pull-db-tests.yml
+++ b/.github/workflows/pull-db-tests.yml
@@ -37,14 +37,6 @@ jobs:
MINIO_ROOT_PASSWORD: 12345678
ports:
- "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:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
diff --git a/assets/go-licenses.json b/assets/go-licenses.json
index ed722b0192..2aa60780c4 100644
--- a/assets/go-licenses.json
+++ b/assets/go-licenses.json
@@ -124,11 +124,6 @@
"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"
},
- {
- "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",
"path": "github.com/beorn7/perks/quantile/LICENSE",
@@ -644,11 +639,6 @@
"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"
},
- {
- "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 terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets \"{}\"\n replaced with your own identifying information. (Don't include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same \"printed page\" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright {yyyy} {name of copyright owner}\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n"
- },
{
"name": "github.com/josharian/intern",
"path": "github.com/josharian/intern/license.md",
@@ -729,11 +719,6 @@
"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"
},
- {
- "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 this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n\n END OF TERMS AND CONDITIONS\n\n APPENDIX: How to apply the Apache License to your work.\n\n To apply the Apache License to your work, attach the following\n boilerplate notice, with the fields enclosed by brackets \"[]\"\n replaced with your own identifying information. (Don't include\n the brackets!) The text should be enclosed in the appropriate\n comment syntax for the file format. We also recommend that a\n file or class name and description of purpose be included on the\n same \"printed page\" as the copyright notice for easier\n identification within third-party archives.\n\n Copyright [yyyy] [name of copyright owner]\n\n Licensed under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License.\n You may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software\n distributed under the License is distributed on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n See the License for the specific language governing permissions and\n limitations under the License.\n"
- },
{
"name": "github.com/mattn/go-colorable",
"path": "github.com/mattn/go-colorable/LICENSE",
@@ -919,16 +904,6 @@
"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"
},
- {
- "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 this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n"
- },
- {
- "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 this License for that Work shall terminate\n as of the date such litigation is filed.\n\n 4. Redistribution. You may reproduce and distribute copies of the\n Work or Derivative Works thereof in any medium, with or without\n modifications, and in Source or Object form, provided that You\n meet the following conditions:\n\n (a) You must give any other recipients of the Work or\n Derivative Works a copy of this License; and\n\n (b) You must cause any modified files to carry prominent notices\n stating that You changed the files; and\n\n (c) You must retain, in the Source form of any Derivative Works\n that You distribute, all copyright, patent, trademark, and\n attribution notices from the Source form of the Work,\n excluding those notices that do not pertain to any part of\n the Derivative Works; and\n\n (d) If the Work includes a \"NOTICE\" text file as part of its\n distribution, then any Derivative Works that You distribute must\n include a readable copy of the attribution notices contained\n within such NOTICE file, excluding those notices that do not\n pertain to any part of the Derivative Works, in at least one\n of the following places: within a NOTICE text file distributed\n as part of the Derivative Works; within the Source form or\n documentation, if provided along with the Derivative Works; or,\n within a display generated by the Derivative Works, if and\n wherever such third-party notices normally appear. The contents\n of the NOTICE file are for informational purposes only and\n do not modify the License. You may add Your own attribution\n notices within Derivative Works that You distribute, alongside\n or as an addendum to the NOTICE text from the Work, provided\n that such additional attribution notices cannot be construed\n as modifying the License.\n\n You may add Your own copyright statement to Your modifications and\n may provide additional or different license terms and conditions\n for use, reproduction, or distribution of Your modifications, or\n for any such Derivative Works as a whole, provided Your use,\n reproduction, and distribution of the Work otherwise complies with\n the conditions stated in this License.\n\n 5. Submission of Contributions. Unless You explicitly state otherwise,\n any Contribution intentionally submitted for inclusion in the Work\n by You to the Licensor shall be under the terms and conditions of\n this License, without any additional terms or conditions.\n Notwithstanding the above, nothing herein shall supersede or modify\n the terms of any separate license agreement you may have executed\n with Licensor regarding such Contributions.\n\n 6. Trademarks. This License does not grant permission to use the trade\n names, trademarks, service marks, or product names of the Licensor,\n except as required for reasonable and customary use in describing the\n origin of the Work and reproducing the content of the NOTICE file.\n\n 7. Disclaimer of Warranty. Unless required by applicable law or\n agreed to in writing, Licensor provides the Work (and each\n Contributor provides its Contributions) on an \"AS IS\" BASIS,\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n implied, including, without limitation, any warranties or conditions\n of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n PARTICULAR PURPOSE. You are solely responsible for determining the\n appropriateness of using or redistributing the Work and assume any\n risks associated with Your exercise of permissions under this License.\n\n 8. Limitation of Liability. In no event and under no legal theory,\n whether in tort (including negligence), contract, or otherwise,\n unless required by applicable law (such as deliberate and grossly\n negligent acts) or agreed to in writing, shall any Contributor be\n liable to You for damages, including any direct, indirect, special,\n incidental, or consequential damages of any character arising as a\n result of this License or out of the use or inability to use the\n Work (including but not limited to damages for loss of goodwill,\n work stoppage, computer failure or malfunction, or any and all\n other commercial damages or losses), even if such Contributor\n has been advised of the possibility of such damages.\n\n 9. Accepting Warranty or Additional Liability. While redistributing\n the Work or Derivative Works thereof, You may choose to offer,\n and charge a fee for, acceptance of support, warranty, indemnity,\n or other liability obligations and/or rights consistent with this\n License. However, in accepting such obligations, You may act only\n on Your own behalf and on Your sole responsibility, not on behalf\n of any other Contributor, and only if You agree to indemnify,\n defend, and hold each Contributor harmless for any liability\n incurred by, or claims asserted against, such Contributor by reason\n of your accepting any such warranty or additional liability.\n"
- },
{
"name": "github.com/russross/blackfriday/v2",
"path": "github.com/russross/blackfriday/v2/LICENSE.txt",
diff --git a/docs/content/usage/authentication.en-us.md b/docs/content/usage/authentication.en-us.md
index 1838cfcc77..adc936dfbe 100644
--- a/docs/content/usage/authentication.en-us.md
+++ b/docs/content/usage/authentication.en-us.md
@@ -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.
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`.
diff --git a/go.mod b/go.mod
index 012a34612f..7a752ec874 100644
--- a/go.mod
+++ b/go.mod
@@ -91,8 +91,6 @@ require (
github.com/quasoft/websspi v1.1.2
github.com/redis/go-redis/v9 v9.4.0
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/sassoftware/go-rpmutils v0.2.1-0.20240124161140-277b154961dd
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/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // 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/bits-and-blooms/bitset v1.13.0 // 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/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // 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/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
@@ -229,7 +225,6 @@ require (
github.com/magiconair/properties v1.8.7 // indirect
github.com/mailru/easyjson v0.7.7 // 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-runewidth v0.0.15 // indirect
github.com/mholt/acmez v1.2.0 // indirect
diff --git a/go.sum b/go.sum
index 393e10cfa0..b3b8ad8ce4 100644
--- a/go.sum
+++ b/go.sum
@@ -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/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/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/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
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/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
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/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
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/goth v1.78.0 h1:7VEIFDycJp9deyVv3YraGBPdD0ZYQW93Y3Aw1eVP3BY=
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/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
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/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.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.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/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
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/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
diff --git a/models/auth/oauth2.go b/models/auth/oauth2.go
index a252458d4e..9d53fffc78 100644
--- a/models/auth/oauth2.go
+++ b/models/auth/oauth2.go
@@ -8,7 +8,6 @@ import (
"crypto/sha256"
"encoding/base32"
"encoding/base64"
- "encoding/gob"
"fmt"
"net"
"net/url"
@@ -82,10 +81,6 @@ func Init(ctx context.Context) error {
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
if err := db.GetEngine(ctx).In("client_id", builtinAllClientIDs).Find(®isteredApps); err != nil {
return err
@@ -610,6 +605,21 @@ func (err ErrOAuthApplicationNotFound) Unwrap() error {
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 {
deleteCond := builder.Select("id").From("oauth2_grant").Where(builder.Eq{"oauth2_grant.user_id": userID})
diff --git a/models/auth/source.go b/models/auth/source.go
index bc564d35ba..1bdde8235c 100644
--- a/models/auth/source.go
+++ b/models/auth/source.go
@@ -14,7 +14,6 @@ import (
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
- "github.com/markbates/goth"
"xorm.io/builder"
"xorm.io/xorm"
"xorm.io/xorm/convert"
@@ -33,7 +32,6 @@ const (
DLDAP // 5
OAuth2 // 6
SSPI // 7
- SAML // 8
)
// String returns the string name of the LoginType
@@ -54,7 +52,6 @@ var Names = map[Type]string{
PAM: "PAM",
OAuth2: "OAuth2",
SSPI: "SPNEGO with SSPI",
- SAML: "SAML",
}
// Config represents login config as far as the db is concerned
@@ -124,12 +121,6 @@ type Source struct {
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
func (Source) TableName() string {
return "login_source"
@@ -189,11 +180,6 @@ func (source *Source) IsSSPI() bool {
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.
func (source *Source) HasTLS() bool {
hasTLSer, ok := source.Cfg.(HasTLSer)
@@ -406,27 +392,3 @@ func IsErrSourceInUse(err error) bool {
func (err ErrSourceInUse) Error() string {
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
-}
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index b35672eac2..2c92f40a17 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -523,9 +523,6 @@ Content = Content
SSPISeparatorReplacement = Separator
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.`
alpha_dash_error = ` should contain only alphanumeric, dash ('-') and underscore ('_') 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_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.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.saml = Documentation can be found at https://docs.gitea.com/usage/authentication#saml
auths.tips.oauth2.general = OAuth2 Authentication
auths.tips.oauth2.general.tip = When registering a new OAuth2 authentication, the callback/redirect URL should be:
auths.tip.oauth2_provider = OAuth2 Provider
diff --git a/routers/init.go b/routers/init.go
index 9ae8c368a2..e0a7150ba3 100644
--- a/routers/init.go
+++ b/routers/init.go
@@ -35,7 +35,6 @@ import (
actions_service "code.gitea.io/gitea/services/actions"
"code.gitea.io/gitea/services/auth"
"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/cron"
feed_service "code.gitea.io/gitea/services/feed"
@@ -139,7 +138,6 @@ func InitWebInstalled(ctx context.Context) {
log.Info("ORM engine initialization successful!")
mustInit(system.Init)
mustInitCtx(ctx, oauth2.Init)
- mustInitCtx(ctx, saml.Init)
mustInit(release_service.Init)
diff --git a/routers/web/admin/auths.go b/routers/web/admin/auths.go
index 187b569d39..7fdd18dfae 100644
--- a/routers/web/admin/auths.go
+++ b/routers/web/admin/auths.go
@@ -1,12 +1,9 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
-// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package admin
import (
- "crypto/tls"
- "crypto/x509"
"errors"
"fmt"
"net/http"
@@ -28,7 +25,6 @@ import (
"code.gitea.io/gitea/services/auth/source/ldap"
"code.gitea.io/gitea/services/auth/source/oauth2"
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/sspi"
"code.gitea.io/gitea/services/forms"
@@ -75,7 +71,6 @@ var (
{auth.SMTP.String(), auth.SMTP},
{auth.OAuth2.String(), auth.OAuth2},
{auth.SSPI.String(), auth.SSPI},
- {auth.SAML.String(), auth.SAML},
}
if pam.Supported {
items = append(items, dropdownItem{auth.Names[auth.PAM], auth.PAM})
@@ -88,16 +83,6 @@ var (
{ldap.SecurityProtocolNames[ldap.SecurityProtocolLDAPS], ldap.SecurityProtocolLDAPS},
{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
@@ -113,8 +98,6 @@ func NewAuthSource(ctx *context.Context) {
ctx.Data["is_sync_enabled"] = true
ctx.Data["AuthSources"] = authSources
ctx.Data["SecurityProtocols"] = securityProtocols
- ctx.Data["CurrentNameIDFormat"] = saml.NameIDFormatNames[saml.SAML20Persistent]
- ctx.Data["NameIDFormats"] = nameIDFormats
ctx.Data["SMTPAuths"] = smtp.Authenticators
oauth2providers := oauth2.GetSupportedOAuth2Providers()
ctx.Data["OAuth2Providers"] = oauth2providers
@@ -248,52 +231,6 @@ func parseSSPIConfig(ctx *context.Context, form forms.AuthenticationForm) (*sspi
}, 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
func NewAuthSourcePost(ctx *context.Context) {
form := *web.GetForm(ctx).(*forms.AuthenticationForm)
@@ -307,8 +244,6 @@ func NewAuthSourcePost(ctx *context.Context) {
ctx.Data["SMTPAuths"] = smtp.Authenticators
oauth2providers := oauth2.GetSupportedOAuth2Providers()
ctx.Data["OAuth2Providers"] = oauth2providers
- ctx.Data["CurrentNameIDFormat"] = saml.NameIDFormatNames[saml.NameIDFormat(form.NameIDFormat)]
- ctx.Data["NameIDFormats"] = nameIDFormats
ctx.Data["SSPIAutoCreateUsers"] = 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)
return
}
- case auth.SAML:
- var err error
- config, err = parseSAMLConfig(ctx, form)
- if err != nil {
- ctx.RenderWithErr(err.Error(), tplAuthNew, form)
- return
- }
default:
ctx.Error(http.StatusBadRequest)
return
@@ -408,7 +336,6 @@ func EditAuthSource(ctx *context.Context) {
ctx.Data["SMTPAuths"] = smtp.Authenticators
oauth2providers := oauth2.GetSupportedOAuth2Providers()
ctx.Data["OAuth2Providers"] = oauth2providers
- ctx.Data["NameIDFormats"] = nameIDFormats
source, err := auth.GetSourceByID(ctx, ctx.ParamsInt64(":authid"))
if err != nil {
@@ -417,9 +344,6 @@ func EditAuthSource(ctx *context.Context) {
}
ctx.Data["Source"] = source
ctx.Data["HasTLS"] = source.HasTLS()
- if source.IsSAML() {
- ctx.Data["CurrentNameIDFormat"] = saml.NameIDFormatNames[source.Cfg.(*saml.Source).NameIDFormat]
- }
if source.IsOAuth2() {
type Named interface {
@@ -454,8 +378,6 @@ func EditAuthSourcePost(ctx *context.Context) {
}
ctx.Data["Source"] = source
ctx.Data["HasTLS"] = source.HasTLS()
- ctx.Data["CurrentNameIDFormat"] = saml.NameIDFormatNames[saml.SAML20Persistent]
- ctx.Data["NameIDFormats"] = nameIDFormats
if ctx.HasError() {
ctx.HTML(http.StatusOK, tplAuthEdit)
@@ -490,12 +412,6 @@ func EditAuthSourcePost(ctx *context.Context) {
ctx.RenderWithErr(err.Error(), tplAuthEdit, form)
return
}
- case auth.SAML:
- config, err = parseSAMLConfig(ctx, form)
- if err != nil {
- ctx.RenderWithErr(err.Error(), tplAuthEdit, form)
- return
- }
default:
ctx.Error(http.StatusBadRequest)
return
diff --git a/routers/web/auth/auth.go b/routers/web/auth/auth.go
index f5955ec5ff..3de1f3373d 100644
--- a/routers/web/auth/auth.go
+++ b/routers/web/auth/auth.go
@@ -28,7 +28,6 @@ import (
"code.gitea.io/gitea/routers/utils"
auth_service "code.gitea.io/gitea/services/auth"
"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/forms"
"code.gitea.io/gitea/services/mailer"
@@ -171,14 +170,6 @@ func SignIn(ctx *context.Context) {
return
}
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["SignInLink"] = setting.AppSubURL + "/user/login"
ctx.Data["PageIsSignIn"] = true
@@ -202,14 +193,6 @@ func SignInPost(ctx *context.Context) {
return
}
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["SignInLink"] = setting.AppSubURL + "/user/login"
ctx.Data["PageIsSignIn"] = true
@@ -521,7 +504,7 @@ func SignUpPost(ctx *context.Context) {
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
return
}
@@ -532,16 +515,16 @@ func SignUpPost(ctx *context.Context) {
// createAndHandleCreatedUser calls createUserInContext and
// 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 {
- if !createUserInContext(ctx, tpl, form, u, overwrites, gothUser, allowLink, authType) {
+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) {
return false
}
- return handleUserCreated(ctx, u, gothUser, authType)
+ return handleUserCreated(ctx, u, gothUser)
}
// createUserInContext creates a user and handles errors within a given context.
// 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 allowLink && (user_model.IsErrUserAlreadyExist(err) || user_model.IsErrEmailAlreadyUsed(err)) {
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...
- linkAccount(ctx, user, *gothUser, true, authType)
+ linkAccount(ctx, user, *gothUser, true)
return false // user is already created here, all redirects are handled
} else if setting.OAuth2Client.AccountLinking == setting.OAuth2AccountLinkingLogin {
- showLinkingLogin(ctx, *gothUser, authType)
+ showLinkingLogin(ctx, *gothUser)
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.
// It auto-sets admin for the only user, updates the optional external user and
// 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.
if user_model.CountUsers(ctx, nil) == 1 {
opts := &user_service.UpdateOptions{
@@ -623,7 +606,7 @@ func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth.
// update external user information
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) {
log.Error("UpdateExternalUser failed: %v", err)
}
diff --git a/routers/web/auth/linkaccount.go b/routers/web/auth/linkaccount.go
index c62ae84083..1d94e52fe3 100644
--- a/routers/web/auth/linkaccount.go
+++ b/routers/web/auth/linkaccount.go
@@ -48,13 +48,13 @@ func LinkAccount(ctx *context.Context) {
ctx.Data["SignInLink"] = setting.AppSubURL + "/user/link_account_signin"
ctx.Data["SignUpLink"] = setting.AppSubURL + "/user/link_account_signup"
- externalLinkUser := ctx.Session.Get("linkAccountUser")
- if externalLinkUser == nil {
+ gothUser := ctx.Session.Get("linkAccountGothUser")
+ if gothUser == nil {
ctx.ServerError("UserSignIn", errors.New("not in LinkAccount session"))
return
}
- gu := externalLinkUser.(auth.LinkAccountUser).GothUser
+ gu, _ := gothUser.(goth.User)
uname, err := getUserName(&gu)
if err != nil {
ctx.ServerError("UserSignIn", err)
@@ -135,14 +135,12 @@ func LinkAccountPostSignIn(ctx *context.Context) {
ctx.Data["SignInLink"] = setting.AppSubURL + "/user/link_account_signin"
ctx.Data["SignUpLink"] = setting.AppSubURL + "/user/link_account_signup"
- externalLinkUserInterface := ctx.Session.Get("linkAccountUser")
- if externalLinkUserInterface == nil {
+ gothUser := ctx.Session.Get("linkAccountGothUser")
+ if gothUser == nil {
ctx.ServerError("UserSignIn", errors.New("not in LinkAccount session"))
return
}
- externalLinkUser := externalLinkUserInterface.(auth.LinkAccountUser)
-
if ctx.HasError() {
ctx.HTML(http.StatusOK, tplLinkAccount)
return
@@ -154,10 +152,10 @@ func LinkAccountPostSignIn(ctx *context.Context) {
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)
// 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
}
- err = externalaccount.LinkAccountToUser(ctx, u, gothUser, authType)
+ err = externalaccount.LinkAccountToUser(ctx, u, gothUser)
if err != nil {
ctx.ServerError("UserLinkAccount", err)
return
@@ -224,14 +222,14 @@ func LinkAccountPostRegister(ctx *context.Context) {
ctx.Data["SignInLink"] = setting.AppSubURL + "/user/link_account_signin"
ctx.Data["SignUpLink"] = setting.AppSubURL + "/user/link_account_signup"
- externalLinkUser := ctx.Session.Get("linkAccountUser")
- if externalLinkUser == nil {
+ gothUserInterface := ctx.Session.Get("linkAccountGothUser")
+ if gothUserInterface == nil {
ctx.ServerError("UserSignUp", errors.New("not in LinkAccount session"))
return
}
- linkUser, ok := externalLinkUser.(auth.LinkAccountUser)
+ gothUser, ok := gothUserInterface.(goth.User)
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
}
@@ -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 {
ctx.ServerError("CreateUser", err)
return
@@ -287,24 +285,21 @@ func LinkAccountPostRegister(ctx *context.Context) {
Name: form.UserName,
Email: form.Email,
Passwd: form.Password,
- LoginType: authSource.Type,
+ LoginType: auth.OAuth2,
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
return
}
- if linkUser.Type == auth.OAuth2 {
- source := authSource.Cfg.(*oauth2.Source)
- if err := syncGroupsToTeams(ctx, source, &linkUser.GothUser, u); err != nil {
- ctx.ServerError("SyncGroupsToTeams", err)
- return
- }
+ source := authSource.Cfg.(*oauth2.Source)
+ if err := syncGroupsToTeams(ctx, source, &gothUser, u); err != nil {
+ ctx.ServerError("SyncGroupsToTeams", err)
+ return
}
- // TODO we will support some form of group mapping for SAML
handleSignIn(ctx, u, false)
}
diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go
index 5e7368eb9a..33a4ae9192 100644
--- a/routers/web/auth/oauth.go
+++ b/routers/web/auth/oauth.go
@@ -841,7 +841,7 @@ func handleAuthorizeError(ctx *context.Context, authErr AuthorizeError, redirect
func SignInOAuth(ctx *context.Context) {
provider := ctx.Params(":provider")
- authSource, err := auth.GetActiveAuthSourceByName(ctx, provider, auth.OAuth2)
+ authSource, err := auth.GetActiveOAuth2SourceByName(ctx, provider)
if err != nil {
ctx.ServerError("SignIn", err)
return
@@ -892,7 +892,7 @@ func SignInOAuthCallback(ctx *context.Context) {
}
// 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 {
ctx.ServerError("SignIn", err)
return
@@ -935,7 +935,7 @@ func SignInOAuthCallback(ctx *context.Context) {
if u == nil {
if ctx.Doer != nil {
// 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 {
ctx.ServerError("UserLinkAccount", err)
return
@@ -988,7 +988,7 @@ func SignInOAuthCallback(ctx *context.Context) {
u.IsAdmin = isAdmin.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
return
}
@@ -999,7 +999,7 @@ func SignInOAuthCallback(ctx *context.Context) {
}
} else {
// no existing user is found, request attach or new account
- showLinkingLogin(ctx, gothUser, auth.OAuth2)
+ showLinkingLogin(ctx, gothUser)
return
}
}
@@ -1063,12 +1063,9 @@ func getUserAdminAndRestrictedFromGroupClaims(source *oauth2.Source, gothUser *g
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{
- "linkAccountUser": auth.LinkAccountUser{
- Type: authType,
- GothUser: gothUser,
- },
+ "linkAccountGothUser": gothUser,
}); err != nil {
ctx.ServerError("updateSession", err)
return
@@ -1147,7 +1144,7 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model
}
// 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) {
log.Error("UpdateExternalUser failed: %v", err)
}
diff --git a/routers/web/auth/openid.go b/routers/web/auth/openid.go
index bf377b4496..29ef772b1c 100644
--- a/routers/web/auth/openid.go
+++ b/routers/web/auth/openid.go
@@ -8,7 +8,6 @@ import (
"net/http"
"net/url"
- auth_model "code.gitea.io/gitea/models/auth"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/auth/openid"
"code.gitea.io/gitea/modules/base"
@@ -364,7 +363,7 @@ func RegisterOpenIDPost(ctx *context.Context) {
Email: form.Email,
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
return
}
@@ -380,7 +379,7 @@ func RegisterOpenIDPost(ctx *context.Context) {
return
}
- if !handleUserCreated(ctx, u, nil, auth_model.NoType) {
+ if !handleUserCreated(ctx, u, nil) {
// error already handled
return
}
diff --git a/routers/web/auth/saml.go b/routers/web/auth/saml.go
deleted file mode 100644
index 29d689d2e9..0000000000
--- a/routers/web/auth/saml.go
+++ /dev/null
@@ -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)
- }
-}
diff --git a/routers/web/web.go b/routers/web/web.go
index 5e18aac67d..a76b444e4f 100644
--- a/routers/web/web.go
+++ b/routers/web/web.go
@@ -667,11 +667,6 @@ func registerRoutes(m *web.Route) {
m.Get("/{provider}", auth.SignInOAuth)
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 *****
diff --git a/services/auth/source/saml/assert_interface_test.go b/services/auth/source/saml/assert_interface_test.go
deleted file mode 100644
index 2ca7057b8a..0000000000
--- a/services/auth/source/saml/assert_interface_test.go
+++ /dev/null
@@ -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{}
diff --git a/services/auth/source/saml/init.go b/services/auth/source/saml/init.go
deleted file mode 100644
index f1d6d9fa4b..0000000000
--- a/services/auth/source/saml/init.go
+++ /dev/null
@@ -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
-}
diff --git a/services/auth/source/saml/name_id_format.go b/services/auth/source/saml/name_id_format.go
deleted file mode 100644
index 1ddf047729..0000000000
--- a/services/auth/source/saml/name_id_format.go
+++ /dev/null
@@ -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)
-}
diff --git a/services/auth/source/saml/providers.go b/services/auth/source/saml/providers.go
deleted file mode 100644
index d0b36ff44d..0000000000
--- a/services/auth/source/saml/providers.go
+++ /dev/null
@@ -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(``,
- 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
-}
diff --git a/services/auth/source/saml/source.go b/services/auth/source/saml/source.go
deleted file mode 100644
index 52388646b5..0000000000
--- a/services/auth/source/saml/source.go
+++ /dev/null
@@ -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 `` or ``. 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 ".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{})
-}
diff --git a/services/auth/source/saml/source_authenticate.go b/services/auth/source/saml/source_authenticate.go
deleted file mode 100644
index d118917f87..0000000000
--- a/services/auth/source/saml/source_authenticate.go
+++ /dev/null
@@ -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)
-}
diff --git a/services/auth/source/saml/source_callout.go b/services/auth/source/saml/source_callout.go
deleted file mode 100644
index 5366f8a527..0000000000
--- a/services/auth/source/saml/source_callout.go
+++ /dev/null
@@ -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
-}
diff --git a/services/auth/source/saml/source_metadata.go b/services/auth/source/saml/source_metadata.go
deleted file mode 100644
index 9fb8c758e3..0000000000
--- a/services/auth/source/saml/source_metadata.go
+++ /dev/null
@@ -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
-}
diff --git a/services/auth/source/saml/source_register.go b/services/auth/source/saml/source_register.go
deleted file mode 100644
index 93eaaa88b6..0000000000
--- a/services/auth/source/saml/source_register.go
+++ /dev/null
@@ -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
-}
diff --git a/services/externalaccount/link.go b/services/externalaccount/link.go
index 1f4c6728b8..d6e2ea7e94 100644
--- a/services/externalaccount/link.go
+++ b/services/externalaccount/link.go
@@ -7,8 +7,9 @@ import (
"context"
"fmt"
- "code.gitea.io/gitea/models/auth"
user_model "code.gitea.io/gitea/models/user"
+
+ "github.com/markbates/goth"
)
// Store represents a thing that stores things
@@ -20,12 +21,10 @@ type Store interface {
// LinkAccountFromStore links the provided user with a stored external user
func LinkAccountFromStore(ctx context.Context, store Store, user *user_model.User) error {
- externalLinkUserInterface := store.Get("linkAccountUser")
- if externalLinkUserInterface == nil {
+ gothUser := store.Get("linkAccountGothUser")
+ if gothUser == nil {
return fmt.Errorf("not in LinkAccount session")
}
- externalLinkUser := externalLinkUserInterface.(auth.LinkAccountUser)
-
- return LinkAccountToUser(ctx, user, externalLinkUser.GothUser, externalLinkUser.Type)
+ return LinkAccountToUser(ctx, user, gothUser.(goth.User))
}
diff --git a/services/externalaccount/user.go b/services/externalaccount/user.go
index fa85a65669..e2de41da18 100644
--- a/services/externalaccount/user.go
+++ b/services/externalaccount/user.go
@@ -16,8 +16,8 @@ import (
"github.com/markbates/goth"
)
-func toExternalLoginUser(ctx context.Context, user *user_model.User, gothUser goth.User, authType auth.Type) (*user_model.ExternalLoginUser, error) {
- authSource, err := auth.GetActiveAuthSourceByName(ctx, gothUser.Provider, authType)
+func toExternalLoginUser(ctx context.Context, user *user_model.User, gothUser goth.User) (*user_model.ExternalLoginUser, error) {
+ authSource, err := auth.GetActiveOAuth2SourceByName(ctx, gothUser.Provider)
if err != nil {
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
-func LinkAccountToUser(ctx context.Context, user *user_model.User, gothUser goth.User, authType auth.Type) error {
- externalLoginUser, err := toExternalLoginUser(ctx, user, gothUser, authType)
+func LinkAccountToUser(ctx context.Context, user *user_model.User, gothUser goth.User) error {
+ externalLoginUser, err := toExternalLoginUser(ctx, user, gothUser)
if err != nil {
return err
}
@@ -71,8 +71,8 @@ func LinkAccountToUser(ctx context.Context, user *user_model.User, gothUser goth
}
// UpdateExternalUser updates external user's information
-func UpdateExternalUser(ctx context.Context, user *user_model.User, gothUser goth.User, authType auth.Type) error {
- externalLoginUser, err := toExternalLoginUser(ctx, user, gothUser, authType)
+func UpdateExternalUser(ctx context.Context, user *user_model.User, gothUser goth.User) error {
+ externalLoginUser, err := toExternalLoginUser(ctx, user, gothUser)
if err != nil {
return err
}
diff --git a/services/forms/auth_form.go b/services/forms/auth_form.go
index 85be38b403..25acbbb99e 100644
--- a/services/forms/auth_form.go
+++ b/services/forms/auth_form.go
@@ -1,4 +1,3 @@
-// Copyright 2023 The Gitea Authors. All rights reserved.
// Copyright 2014 The Gogs Authors. All rights reserved.
// SPDX-License-Identifier: MIT
@@ -16,7 +15,7 @@ import (
// AuthenticationForm form for authentication
type AuthenticationForm struct {
ID int64
- Type int `binding:"Range(2,9)"`
+ Type int `binding:"Range(2,7)"`
Name string `binding:"Required;MaxSize(30)"`
Host string
Port int
@@ -83,18 +82,6 @@ type AuthenticationForm struct {
SSPIDefaultLanguage string
GroupTeamMap string `binding:"ValidGroupTeamMap"`
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
diff --git a/templates/admin/auth/edit.tmpl b/templates/admin/auth/edit.tmpl
index 2182d011e9..25abefae00 100644
--- a/templates/admin/auth/edit.tmpl
+++ b/templates/admin/auth/edit.tmpl
@@ -367,69 +367,6 @@
diff --git a/tests/integration/README.md b/tests/integration/README.md
index c691483511..f6f74ca21f 100644
--- a/tests/integration/README.md
+++ b/tests/integration/README.md
@@ -110,20 +110,3 @@ SLOW_FLUSH = 5S ; 5s is the default value
```bash
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
-```
diff --git a/tests/integration/saml_test.go b/tests/integration/saml_test.go
deleted file mode 100644
index 585fd35c5f..0000000000
--- a/tests/integration/saml_test.go
+++ /dev/null
@@ -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(``)
- 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)
-}
diff --git a/web_src/js/features/admin/common.js b/web_src/js/features/admin/common.js
index 4804163971..044976ea7b 100644
--- a/web_src/js/features/admin/common.js
+++ b/web_src/js/features/admin/common.js
@@ -103,9 +103,9 @@ export function initAdminCommon() {
// New authentication
if ($('.admin.new.authentication').length > 0) {
$('#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');
const authType = $(this).val();
@@ -137,10 +137,6 @@ export function initAdminCommon() {
showElem($('.sspi'));
$('.sspi div.required input').attr('required', 'required');
break;
- case '8': // SAML
- showElem($('.saml'));
- $('.saml div.required input').attr('required', 'required');
- break;
}
if (authType === '2' || authType === '5') {
onSecurityProtocolChange();
diff --git a/web_src/js/features/user-auth.js b/web_src/js/features/user-auth.js
index 3bf84e31df..60d186e699 100644
--- a/web_src/js/features/user-auth.js
+++ b/web_src/js/features/user-auth.js
@@ -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);
- });
- }
-}
diff --git a/web_src/js/index.js b/web_src/js/index.js
index 876e4291ee..d9cfff4084 100644
--- a/web_src/js/index.js
+++ b/web_src/js/index.js
@@ -23,10 +23,7 @@ import {initFindFileInRepo} from './features/repo-findfile.js';
import {initCommentContent, initMarkupContent} from './markup/content.js';
import {initPdfViewer} from './render/pdf.js';
-import {
- initUserAuthOauth2,
- initUserAuthSAML
-} from './features/user-auth.js';
+import {initUserAuthOauth2} from './features/user-auth.js';
import {
initRepoIssueDue,
initRepoIssueReferenceRepositorySearch,
@@ -184,7 +181,6 @@ onDomReady(() => {
initCaptcha();
initUserAuthOauth2();
- initUserAuthSAML();
initUserAuthWebAuthn();
initUserAuthWebAuthnRegister();
initUserSettings();
From b79c30435f439af8243ee281310258cdf141e27b Mon Sep 17 00:00:00 2001
From: Lunny Xiao
Date: Sat, 24 Feb 2024 14:55:19 +0800
Subject: [PATCH 19/79] Use the database object format name but not read from
git repoisitory everytime and fix possible migration wrong objectformat when
migrating a sha256 repository (#29294)
Now we can get object format name from git command line or from the
database repository table. Assume the column is right, we don't need to
read from git command line every time.
This also fixed a possible bug that the object format is wrong when
migrating a sha256 repository from external.
---
modules/context/api.go | 9 ++-------
modules/context/repo.go | 20 ++++++++------------
routers/api/v1/utils/git.go | 2 +-
routers/private/hook_pre_receive.go | 2 +-
routers/web/repo/blame.go | 7 ++-----
routers/web/repo/compare.go | 4 ++--
routers/web/repo/setting/lfs.go | 2 +-
services/agit/agit.go | 3 +--
services/migrations/gitea_uploader.go | 16 +++++++++++++---
services/pull/check.go | 5 +----
services/pull/merge.go | 2 +-
services/release/release.go | 2 +-
services/repository/branch.go | 7 ++-----
services/repository/files/commit.go | 6 ++----
services/repository/files/tree.go | 2 +-
services/repository/lfs.go | 2 +-
services/repository/push.go | 6 +-----
17 files changed, 41 insertions(+), 56 deletions(-)
diff --git a/modules/context/api.go b/modules/context/api.go
index f8bc682fed..b18a206b5e 100644
--- a/modules/context/api.go
+++ b/modules/context/api.go
@@ -307,12 +307,6 @@ func RepoRefForAPI(next http.Handler) http.Handler {
return
}
- objectFormat, err := ctx.Repo.GitRepo.GetObjectFormat()
- if err != nil {
- ctx.Error(http.StatusInternalServerError, "GetCommit", err)
- return
- }
-
if ref := ctx.FormTrim("ref"); len(ref) > 0 {
commit, err := ctx.Repo.GitRepo.GetCommit(ref)
if err != nil {
@@ -331,6 +325,7 @@ func RepoRefForAPI(next http.Handler) http.Handler {
}
refName := getRefName(ctx.Base, ctx.Repo, RepoRefAny)
+ var err error
if ctx.Repo.GitRepo.IsBranchExist(refName) {
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName)
@@ -346,7 +341,7 @@ func RepoRefForAPI(next http.Handler) http.Handler {
return
}
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
- } else if len(refName) == objectFormat.FullLength() {
+ } else if len(refName) == ctx.Repo.GetObjectFormat().FullLength() {
ctx.Repo.CommitID = refName
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refName)
if err != nil {
diff --git a/modules/context/repo.go b/modules/context/repo.go
index 8508d46cf4..a73d09ee21 100644
--- a/modules/context/repo.go
+++ b/modules/context/repo.go
@@ -83,6 +83,10 @@ func (r *Repository) CanCreateBranch() bool {
return r.Permission.CanWrite(unit_model.TypeCode) && r.Repository.CanCreateBranch()
}
+func (r *Repository) GetObjectFormat() git.ObjectFormat {
+ return git.ObjectFormatFromName(r.Repository.ObjectFormatName)
+}
+
// RepoMustNotBeArchived checks if a repo is archived
func RepoMustNotBeArchived() func(ctx *Context) {
return func(ctx *Context) {
@@ -830,9 +834,8 @@ func getRefName(ctx *Base, repo *Repository, pathType RepoRefType) string {
}
// For legacy and API support only full commit sha
parts := strings.Split(path, "/")
- objectFormat, _ := repo.GitRepo.GetObjectFormat()
- if len(parts) > 0 && len(parts[0]) == objectFormat.FullLength() {
+ if len(parts) > 0 && len(parts[0]) == git.ObjectFormatFromName(repo.Repository.ObjectFormatName).FullLength() {
repo.TreePath = strings.Join(parts[1:], "/")
return parts[0]
}
@@ -876,9 +879,8 @@ func getRefName(ctx *Base, repo *Repository, pathType RepoRefType) string {
return getRefNameFromPath(ctx, repo, path, repo.GitRepo.IsTagExist)
case RepoRefCommit:
parts := strings.Split(path, "/")
- objectFormat, _ := repo.GitRepo.GetObjectFormat()
- if len(parts) > 0 && len(parts[0]) >= 7 && len(parts[0]) <= objectFormat.FullLength() {
+ if len(parts) > 0 && len(parts[0]) >= 7 && len(parts[0]) <= repo.GetObjectFormat().FullLength() {
repo.TreePath = strings.Join(parts[1:], "/")
return parts[0]
}
@@ -937,12 +939,6 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
}
}
- objectFormat, err := ctx.Repo.GitRepo.GetObjectFormat()
- if err != nil {
- log.Error("Cannot determine objectFormat for repository: %w", err)
- ctx.Repo.Repository.MarkAsBrokenEmpty()
- }
-
// Get default branch.
if len(ctx.Params("*")) == 0 {
refName = ctx.Repo.Repository.DefaultBranch
@@ -1009,7 +1005,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
return cancel
}
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
- } else if len(refName) >= 7 && len(refName) <= objectFormat.FullLength() {
+ } else if len(refName) >= 7 && len(refName) <= ctx.Repo.GetObjectFormat().FullLength() {
ctx.Repo.IsViewCommit = true
ctx.Repo.CommitID = refName
@@ -1019,7 +1015,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
return cancel
}
// If short commit ID add canonical link header
- if len(refName) < objectFormat.FullLength() {
+ if len(refName) < ctx.Repo.GetObjectFormat().FullLength() {
ctx.RespHeader().Set("Link", fmt.Sprintf("<%s>; rel=\"canonical\"",
util.URLJoin(setting.AppURL, strings.Replace(ctx.Req.URL.RequestURI(), util.PathEscapeSegments(refName), url.PathEscape(ctx.Repo.Commit.ID.String()), 1))))
}
diff --git a/routers/api/v1/utils/git.go b/routers/api/v1/utils/git.go
index 2299cdc247..5e80190017 100644
--- a/routers/api/v1/utils/git.go
+++ b/routers/api/v1/utils/git.go
@@ -72,7 +72,7 @@ func searchRefCommitByType(ctx *context.APIContext, refType, filter string) (str
// ConvertToObjectID returns a full-length SHA1 from a potential ID string
func ConvertToObjectID(ctx gocontext.Context, repo *context.Repository, commitID string) (git.ObjectID, error) {
- objectFormat, _ := repo.GitRepo.GetObjectFormat()
+ objectFormat := repo.GetObjectFormat()
if len(commitID) == objectFormat.FullLength() && objectFormat.IsValid(commitID) {
sha, err := git.NewIDFromString(commitID)
if err == nil {
diff --git a/routers/private/hook_pre_receive.go b/routers/private/hook_pre_receive.go
index 90d8287f06..f28ae4c0eb 100644
--- a/routers/private/hook_pre_receive.go
+++ b/routers/private/hook_pre_receive.go
@@ -145,7 +145,7 @@ func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID string, r
repo := ctx.Repo.Repository
gitRepo := ctx.Repo.GitRepo
- objectFormat, _ := gitRepo.GetObjectFormat()
+ objectFormat := ctx.Repo.GetObjectFormat()
if branchName == repo.DefaultBranch && newCommitID == objectFormat.EmptyObjectID().String() {
log.Warn("Forbidden: Branch: %s is the default branch in %-v and cannot be deleted", branchName, repo)
diff --git a/routers/web/repo/blame.go b/routers/web/repo/blame.go
index c7875ea0cb..7602b30d2b 100644
--- a/routers/web/repo/blame.go
+++ b/routers/web/repo/blame.go
@@ -132,11 +132,8 @@ type blameResult struct {
}
func performBlame(ctx *context.Context, repoPath string, commit *git.Commit, file string, bypassBlameIgnore bool) (*blameResult, error) {
- objectFormat, err := ctx.Repo.GitRepo.GetObjectFormat()
- if err != nil {
- ctx.NotFound("CreateBlameReader", err)
- return nil, err
- }
+ objectFormat := ctx.Repo.GetObjectFormat()
+
blameReader, err := git.CreateBlameReader(ctx, objectFormat, repoPath, commit, file, bypassBlameIgnore)
if err != nil {
return nil, err
diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go
index df41c750de..535487d5fd 100644
--- a/routers/web/repo/compare.go
+++ b/routers/web/repo/compare.go
@@ -312,14 +312,14 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo {
baseIsCommit := ctx.Repo.GitRepo.IsCommitExist(ci.BaseBranch)
baseIsBranch := ctx.Repo.GitRepo.IsBranchExist(ci.BaseBranch)
baseIsTag := ctx.Repo.GitRepo.IsTagExist(ci.BaseBranch)
- objectFormat, _ := ctx.Repo.GitRepo.GetObjectFormat()
+
if !baseIsCommit && !baseIsBranch && !baseIsTag {
// Check if baseBranch is short sha commit hash
if baseCommit, _ := ctx.Repo.GitRepo.GetCommit(ci.BaseBranch); baseCommit != nil {
ci.BaseBranch = baseCommit.ID.String()
ctx.Data["BaseBranch"] = ci.BaseBranch
baseIsCommit = true
- } else if ci.BaseBranch == objectFormat.EmptyObjectID().String() {
+ } else if ci.BaseBranch == ctx.Repo.GetObjectFormat().EmptyObjectID().String() {
if isSameRepo {
ctx.Redirect(ctx.Repo.RepoLink + "/compare/" + util.PathEscapeSegments(ci.HeadBranch))
} else {
diff --git a/routers/web/repo/setting/lfs.go b/routers/web/repo/setting/lfs.go
index cd0f11d548..76a90a4ac5 100644
--- a/routers/web/repo/setting/lfs.go
+++ b/routers/web/repo/setting/lfs.go
@@ -388,7 +388,7 @@ func LFSFileFind(ctx *context.Context) {
sha := ctx.FormString("sha")
ctx.Data["Title"] = oid
ctx.Data["PageIsSettingsLFS"] = true
- objectFormat, _ := ctx.Repo.GitRepo.GetObjectFormat()
+ objectFormat := ctx.Repo.GetObjectFormat()
var objectID git.ObjectID
if len(sha) == 0 {
pointer := lfs.Pointer{Oid: oid, Size: size}
diff --git a/services/agit/agit.go b/services/agit/agit.go
index 75b561581d..2233fe8547 100644
--- a/services/agit/agit.go
+++ b/services/agit/agit.go
@@ -36,7 +36,7 @@ func ProcReceive(ctx context.Context, repo *repo_model.Repository, gitRepo *git.
topicBranch = opts.GitPushOptions["topic"]
_, forcePush = opts.GitPushOptions["force-push"]
- objectFormat, _ := gitRepo.GetObjectFormat()
+ objectFormat := git.ObjectFormatFromName(repo.ObjectFormatName)
pusher, err := user_model.GetUserByID(ctx, opts.UserID)
if err != nil {
@@ -149,7 +149,6 @@ func ProcReceive(ctx context.Context, repo *repo_model.Repository, gitRepo *git.
log.Trace("Pull request created: %d/%d", repo.ID, prIssue.ID)
- objectFormat, _ := gitRepo.GetObjectFormat()
results = append(results, private.HookProcReceiveRefResult{
Ref: pr.GetGitRefName(),
OriginalRef: opts.RefFullNames[i],
diff --git a/services/migrations/gitea_uploader.go b/services/migrations/gitea_uploader.go
index 2891977c7c..468be6c9df 100644
--- a/services/migrations/gitea_uploader.go
+++ b/services/migrations/gitea_uploader.go
@@ -140,8 +140,18 @@ func (g *GiteaLocalUploader) CreateRepo(repo *base.Repository, opts base.Migrate
if err != nil {
return err
}
- g.gitRepo, err = gitrepo.OpenRepository(g.ctx, r)
- return err
+ g.gitRepo, err = gitrepo.OpenRepository(g.ctx, g.repo)
+ if err != nil {
+ return err
+ }
+
+ // detect object format from git repository and update to database
+ objectFormat, err := g.gitRepo.GetObjectFormat()
+ if err != nil {
+ return err
+ }
+ g.repo.ObjectFormatName = objectFormat.Name()
+ return repo_model.UpdateRepositoryCols(g.ctx, g.repo, "object_format_name")
}
// Close closes this uploader
@@ -896,7 +906,7 @@ func (g *GiteaLocalUploader) CreateReviews(reviews ...*base.Review) error {
comment.UpdatedAt = comment.CreatedAt
}
- objectFormat, _ := g.gitRepo.GetObjectFormat()
+ objectFormat := git.ObjectFormatFromName(g.repo.ObjectFormatName)
if !objectFormat.IsValid(comment.CommitID) {
log.Warn("Invalid comment CommitID[%s] on comment[%d] in PR #%d of %s/%s replaced with %s", comment.CommitID, pr.Index, g.repoOwner, g.repoName, headCommitID)
comment.CommitID = headCommitID
diff --git a/services/pull/check.go b/services/pull/check.go
index dd6c3ed230..f4dd332b14 100644
--- a/services/pull/check.go
+++ b/services/pull/check.go
@@ -222,10 +222,7 @@ func getMergeCommit(ctx context.Context, pr *issues_model.PullRequest) (*git.Com
}
defer gitRepo.Close()
- objectFormat, err := gitRepo.GetObjectFormat()
- if err != nil {
- return nil, fmt.Errorf("%-v GetObjectFormat: %w", pr.BaseRepo, err)
- }
+ objectFormat := git.ObjectFormatFromName(pr.BaseRepo.ObjectFormatName)
// Get the commit from BaseBranch where the pull request got merged
mergeCommit, _, err := git.NewCommand(ctx, "rev-list", "--ancestry-path", "--merges", "--reverse").
diff --git a/services/pull/merge.go b/services/pull/merge.go
index d4c0c821d6..e37540a96f 100644
--- a/services/pull/merge.go
+++ b/services/pull/merge.go
@@ -497,7 +497,7 @@ func MergedManually(ctx context.Context, pr *issues_model.PullRequest, doer *use
return models.ErrInvalidMergeStyle{ID: pr.BaseRepo.ID, Style: repo_model.MergeStyleManuallyMerged}
}
- objectFormat, _ := baseGitRepo.GetObjectFormat()
+ objectFormat := git.ObjectFormatFromName(pr.BaseRepo.ObjectFormatName)
if len(commitID) != objectFormat.FullLength() {
return fmt.Errorf("Wrong commit ID")
}
diff --git a/services/release/release.go b/services/release/release.go
index 4c522c18be..a359e5078e 100644
--- a/services/release/release.go
+++ b/services/release/release.go
@@ -88,7 +88,7 @@ func createTag(ctx context.Context, gitRepo *git.Repository, rel *repo_model.Rel
created = true
rel.LowerTagName = strings.ToLower(rel.TagName)
- objectFormat, _ := gitRepo.GetObjectFormat()
+ objectFormat := git.ObjectFormatFromName(rel.Repo.ObjectFormatName)
commits := repository.NewPushCommits()
commits.HeadCommit = repository.CommitToPushCommit(commit)
commits.CompareURL = rel.Repo.ComposeCompareURL(objectFormat.EmptyObjectID().String(), commit.ID.String())
diff --git a/services/repository/branch.go b/services/repository/branch.go
index 38781acb58..ec41173da8 100644
--- a/services/repository/branch.go
+++ b/services/repository/branch.go
@@ -380,11 +380,6 @@ func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.R
return fmt.Errorf("GetBranch: %vc", err)
}
- objectFormat, err := gitRepo.GetObjectFormat()
- if err != nil {
- return err
- }
-
if rawBranch.IsDeleted {
return nil
}
@@ -406,6 +401,8 @@ func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.R
return err
}
+ objectFormat := git.ObjectFormatFromName(repo.ObjectFormatName)
+
// Don't return error below this
if err := PushUpdate(
&repo_module.PushUpdateOptions{
diff --git a/services/repository/files/commit.go b/services/repository/files/commit.go
index 16a15e06a7..512aec7c81 100644
--- a/services/repository/files/commit.go
+++ b/services/repository/files/commit.go
@@ -30,10 +30,8 @@ func CreateCommitStatus(ctx context.Context, repo *repo_model.Repository, creato
}
defer closer.Close()
- objectFormat, err := gitRepo.GetObjectFormat()
- if err != nil {
- return fmt.Errorf("GetObjectFormat[%s]: %w", repoPath, err)
- }
+ objectFormat := git.ObjectFormatFromName(repo.ObjectFormatName)
+
commit, err := gitRepo.GetCommit(sha)
if err != nil {
gitRepo.Close()
diff --git a/services/repository/files/tree.go b/services/repository/files/tree.go
index 9d3185c3fc..e3a7f3b8b0 100644
--- a/services/repository/files/tree.go
+++ b/services/repository/files/tree.go
@@ -37,7 +37,7 @@ func GetTreeBySHA(ctx context.Context, repo *repo_model.Repository, gitRepo *git
}
apiURL := repo.APIURL()
apiURLLen := len(apiURL)
- objectFormat, _ := gitRepo.GetObjectFormat()
+ objectFormat := git.ObjectFormatFromName(repo.ObjectFormatName)
hashLen := objectFormat.FullLength()
const gitBlobsPath = "/git/blobs/"
diff --git a/services/repository/lfs.go b/services/repository/lfs.go
index 4504f796bd..4d48881b87 100644
--- a/services/repository/lfs.go
+++ b/services/repository/lfs.go
@@ -79,7 +79,7 @@ func GarbageCollectLFSMetaObjectsForRepo(ctx context.Context, repo *repo_model.R
store := lfs.NewContentStore()
errStop := errors.New("STOPERR")
- objectFormat, _ := gitRepo.GetObjectFormat()
+ objectFormat := git.ObjectFormatFromName(repo.ObjectFormatName)
err = git_model.IterateLFSMetaObjectsForRepo(ctx, repo.ID, func(ctx context.Context, metaObject *git_model.LFSMetaObject, count int64) error {
if opts.NumberToCheckPerRepo > 0 && total > opts.NumberToCheckPerRepo {
diff --git a/services/repository/push.go b/services/repository/push.go
index c76025b6a7..9aaf0e1c9b 100644
--- a/services/repository/push.go
+++ b/services/repository/push.go
@@ -93,11 +93,6 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
}
defer gitRepo.Close()
- objectFormat, err := gitRepo.GetObjectFormat()
- if err != nil {
- return fmt.Errorf("unknown repository ObjectFormat [%s]: %w", repo.FullName(), err)
- }
-
if err = repo_module.UpdateRepoSize(ctx, repo); err != nil {
return fmt.Errorf("Failed to update size for repository: %v", err)
}
@@ -105,6 +100,7 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error {
addTags := make([]string, 0, len(optsList))
delTags := make([]string, 0, len(optsList))
var pusher *user_model.User
+ objectFormat := git.ObjectFormatFromName(repo.ObjectFormatName)
for _, opts := range optsList {
log.Trace("pushUpdates: %-v %s %s %s", repo, opts.OldCommitID, opts.NewCommitID, opts.RefFullName)
From 6e5966597c2d498d1a8540dad965461d44ff8e57 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Sebastian=20Br=C3=BCckner?=
Date: Sat, 24 Feb 2024 07:49:16 +0000
Subject: [PATCH 20/79] Properly migrate target branch change GitLab comment
(#29340)
GitLab generates "system notes" whenever an event happens within the
platform. Unlike Gitea, those events are stored and retrieved as text
comments with no semantic details. The only way to tell whether a
comment was generated in this manner is the `system` flag on the note
type.
This PR adds detection for a new specific kind of event: Changing the
target branch of a PR. When detected, it is downloaded using Gitea's
type for this event, and eventually uploaded into Gitea in the expected
format, i.e. with no text content in the comment.
This PR also updates the template used to render comments to add support
for migrated comments of this type.
ref:
https://gitlab.com/gitlab-org/gitlab/-/blob/11bd6dc826e0bea2832324a1d7356949a9398884/app/services/system_notes/merge_requests_service.rb#L102
---
services/migrations/gitea_uploader.go | 10 ++++++++--
services/migrations/gitlab.go | 10 +++++++++-
services/migrations/gitlab_test.go | 19 ++++++++++++++++++-
.../repo/issue/view_content/comments.tmpl | 19 +++++++++++++++----
4 files changed, 50 insertions(+), 8 deletions(-)
diff --git a/services/migrations/gitea_uploader.go b/services/migrations/gitea_uploader.go
index 468be6c9df..8bcf483947 100644
--- a/services/migrations/gitea_uploader.go
+++ b/services/migrations/gitea_uploader.go
@@ -492,10 +492,16 @@ func (g *GiteaLocalUploader) CreateComments(comments ...*base.Comment) error {
}
case issues_model.CommentTypeChangeTitle:
if comment.Meta["OldTitle"] != nil {
- cm.OldTitle = fmt.Sprintf("%s", comment.Meta["OldTitle"])
+ cm.OldTitle = fmt.Sprint(comment.Meta["OldTitle"])
}
if comment.Meta["NewTitle"] != nil {
- cm.NewTitle = fmt.Sprintf("%s", comment.Meta["NewTitle"])
+ cm.NewTitle = fmt.Sprint(comment.Meta["NewTitle"])
+ }
+ case issues_model.CommentTypeChangeTargetBranch:
+ if comment.Meta["OldRef"] != nil && comment.Meta["NewRef"] != nil {
+ cm.OldRef = fmt.Sprint(comment.Meta["OldRef"])
+ cm.NewRef = fmt.Sprint(comment.Meta["NewRef"])
+ cm.Content = ""
}
case issues_model.CommentTypePRScheduledToAutoMerge, issues_model.CommentTypePRUnScheduledToAutoMerge:
cm.Content = ""
diff --git a/services/migrations/gitlab.go b/services/migrations/gitlab.go
index d08eaf0f84..5e49ae6d57 100644
--- a/services/migrations/gitlab.go
+++ b/services/migrations/gitlab.go
@@ -11,6 +11,7 @@ import (
"net/http"
"net/url"
"path"
+ "regexp"
"strings"
"time"
@@ -519,6 +520,8 @@ func (g *GitlabDownloader) GetComments(commentable base.Commentable) ([]*base.Co
return allComments, true, nil
}
+var targetBranchChangeRegexp = regexp.MustCompile("^changed target branch from `(.*?)` to `(.*?)`$")
+
func (g *GitlabDownloader) convertNoteToComment(localIndex int64, note *gitlab.Note) *base.Comment {
comment := &base.Comment{
IssueIndex: localIndex,
@@ -528,11 +531,16 @@ func (g *GitlabDownloader) convertNoteToComment(localIndex int64, note *gitlab.N
PosterEmail: note.Author.Email,
Content: note.Body,
Created: *note.CreatedAt,
+ Meta: map[string]any{},
}
// Try to find the underlying event of system notes.
if note.System {
- if strings.HasPrefix(note.Body, "enabled an automatic merge") {
+ if match := targetBranchChangeRegexp.FindStringSubmatch(note.Body); match != nil {
+ comment.CommentType = issues_model.CommentTypeChangeTargetBranch.String()
+ comment.Meta["OldRef"] = match[1]
+ comment.Meta["NewRef"] = match[2]
+ } else if strings.HasPrefix(note.Body, "enabled an automatic merge") {
comment.CommentType = issues_model.CommentTypePRScheduledToAutoMerge.String()
} else if note.Body == "canceled the automatic merge" {
comment.CommentType = issues_model.CommentTypePRUnScheduledToAutoMerge.String()
diff --git a/services/migrations/gitlab_test.go b/services/migrations/gitlab_test.go
index 2b87a1dfe6..0b9eeaed54 100644
--- a/services/migrations/gitlab_test.go
+++ b/services/migrations/gitlab_test.go
@@ -545,7 +545,8 @@ func TestNoteToComment(t *testing.T) {
notes := []gitlab.Note{
makeTestNote(1, "This is a regular comment", false),
makeTestNote(2, "enabled an automatic merge for abcd1234", true),
- makeTestNote(3, "canceled the automatic merge", true),
+ makeTestNote(3, "changed target branch from `master` to `main`", true),
+ makeTestNote(4, "canceled the automatic merge", true),
}
comments := []base.Comment{{
IssueIndex: 17,
@@ -556,6 +557,7 @@ func TestNoteToComment(t *testing.T) {
CommentType: "",
Content: "This is a regular comment",
Created: now,
+ Meta: map[string]any{},
}, {
IssueIndex: 17,
Index: 2,
@@ -565,15 +567,30 @@ func TestNoteToComment(t *testing.T) {
CommentType: "pull_scheduled_merge",
Content: "enabled an automatic merge for abcd1234",
Created: now,
+ Meta: map[string]any{},
}, {
IssueIndex: 17,
Index: 3,
PosterID: 72,
PosterName: "test",
PosterEmail: "test@example.com",
+ CommentType: "change_target_branch",
+ Content: "changed target branch from `master` to `main`",
+ Created: now,
+ Meta: map[string]any{
+ "OldRef": "master",
+ "NewRef": "main",
+ },
+ }, {
+ IssueIndex: 17,
+ Index: 4,
+ PosterID: 72,
+ PosterName: "test",
+ PosterEmail: "test@example.com",
CommentType: "pull_cancel_scheduled_merge",
Content: "canceled the automatic merge",
Created: now,
+ Meta: map[string]any{},
}}
for i, note := range notes {
diff --git a/templates/repo/issue/view_content/comments.tmpl b/templates/repo/issue/view_content/comments.tmpl
index 597f025470..7bd7e8c35d 100644
--- a/templates/repo/issue/view_content/comments.tmpl
+++ b/templates/repo/issue/view_content/comments.tmpl
@@ -365,8 +365,7 @@
{{else if eq .Type 22}}
- {{if .OriginalAuthor}}
- {{else}}
+ {{if not .OriginalAuthor}}
{{/* Some timeline avatars need a offset to correctly align with their speech
bubble. The condition depends on review type and for positive reviews whether
there is a comment element or not */}}
@@ -495,9 +494,21 @@
{{else if eq .Type 25}}