mirror of
https://github.com/go-gitea/gitea.git
synced 2026-11-09 17:37:20 -06:00
feat(auth)!: remove support for obsolete OpenID 2.0 draft (#40231)
Remove OpenID 2.0 authentication. The spec is an obsolete draft, major providers dropped it years ago, and the code keeps needing security fixes like https://github.com/go-gitea/gitea/pull/39642. OpenID Connect and OAuth2 are unaffected. Docs: https://gitea.com/gitea/docs/pulls/633. - The `[openid]` settings, OpenID sign-in, sign-up and account association are gone - A migration drops the `user_open_id` table with all user OpenID URIs - Accounts registered via OpenID have a random password and regain access via password reset Fixes: https://github.com/go-gitea/gitea/issues/1310 Fixes: https://github.com/go-gitea/gitea/issues/3521
This commit is contained in:
5
assets/go-licenses.json
generated
5
assets/go-licenses.json
generated
@@ -954,11 +954,6 @@
|
|||||||
"path": "github.com/x448/float16/LICENSE",
|
"path": "github.com/x448/float16/LICENSE",
|
||||||
"licenseText": "MIT License\n\nCopyright (c) 2019 Montgomery Edwards⁴⁴⁸ and Faye Amacker\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n"
|
"licenseText": "MIT License\n\nCopyright (c) 2019 Montgomery Edwards⁴⁴⁸ and Faye Amacker\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/yohcop/openid-go",
|
|
||||||
"path": "github.com/yohcop/openid-go/LICENSE",
|
|
||||||
"licenseText": "Copyright 2015 Yohann Coppel\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "github.com/yuin/goldmark",
|
"name": "github.com/yuin/goldmark",
|
||||||
"path": "github.com/yuin/goldmark/LICENSE",
|
"path": "github.com/yuin/goldmark/LICENSE",
|
||||||
|
|||||||
@@ -1721,58 +1721,18 @@ LEVEL = Info
|
|||||||
;; - manage_ssh_keys: a user cannot configure ssh keys
|
;; - manage_ssh_keys: a user cannot configure ssh keys
|
||||||
;; - manage_gpg_keys: a user cannot configure gpg keys
|
;; - manage_gpg_keys: a user cannot configure gpg keys
|
||||||
;; - manage_mfa: a user cannot configure mfa devices
|
;; - manage_mfa: a user cannot configure mfa devices
|
||||||
;; - manage_credentials: a user cannot configure emails, passwords, or openid
|
;; - manage_credentials: a user cannot configure emails or passwords
|
||||||
;USER_DISABLED_FEATURES =
|
;USER_DISABLED_FEATURES =
|
||||||
;; Comma separated list of disabled features ONLY if the user has an external login type (eg. LDAP, Oauth, etc.), could be "deletion", "manage_ssh_keys", "manage_gpg_keys", "manage_mfa", "manage_credentials". This setting is independent from `USER_DISABLED_FEATURES` and supplements its behavior.
|
;; Comma separated list of disabled features ONLY if the user has an external login type (eg. LDAP, Oauth, etc.), could be "deletion", "manage_ssh_keys", "manage_gpg_keys", "manage_mfa", "manage_credentials". This setting is independent from `USER_DISABLED_FEATURES` and supplements its behavior.
|
||||||
;; - deletion: a user cannot delete their own account
|
;; - deletion: a user cannot delete their own account
|
||||||
;; - manage_ssh_keys: a user cannot configure ssh keys
|
;; - manage_ssh_keys: a user cannot configure ssh keys
|
||||||
;; - manage_gpg_keys: a user cannot configure gpg keys
|
;; - manage_gpg_keys: a user cannot configure gpg keys
|
||||||
;; - manage_mfa: a user cannot configure mfa devices
|
;; - manage_mfa: a user cannot configure mfa devices
|
||||||
;; - manage_credentials: a user cannot configure emails, passwords, or openid
|
;; - manage_credentials: a user cannot configure emails or passwords
|
||||||
;; - change_username: a user cannot change their username
|
;; - change_username: a user cannot change their username
|
||||||
;; - change_full_name: a user cannot change their full name
|
;; - change_full_name: a user cannot change their full name
|
||||||
;;EXTERNAL_USER_DISABLE_FEATURES =
|
;;EXTERNAL_USER_DISABLE_FEATURES =
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
;[openid]
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
;;
|
|
||||||
;; OpenID is an open, standard and decentralized authentication protocol.
|
|
||||||
;; Your identity is the address of a webpage you provide, which describes
|
|
||||||
;; how to prove you are in control of that page.
|
|
||||||
;;
|
|
||||||
;; For more info: https://en.wikipedia.org/wiki/OpenID
|
|
||||||
;;
|
|
||||||
;; Current implementation supports OpenID-2.0
|
|
||||||
;;
|
|
||||||
;; Tested to work providers at the time of writing:
|
|
||||||
;; - Any GNUSocial node (your.hostname.tld/username)
|
|
||||||
;; - Any SimpleID provider (http://simpleid.koinic.net)
|
|
||||||
;; - http://openid.org.cn/
|
|
||||||
;; - openid.stackexchange.com
|
|
||||||
;; - login.launchpad.net
|
|
||||||
;; - <username>.livejournal.com
|
|
||||||
;;
|
|
||||||
;; Whether to allow sign-in via OpenID
|
|
||||||
;ENABLE_OPENID_SIGNIN = false
|
|
||||||
;;
|
|
||||||
;; Whether to allow registering via OpenID
|
|
||||||
;;ENABLE_OPENID_SIGNUP = false
|
|
||||||
;;
|
|
||||||
;; Allowed URI patterns (POSIX regexp).
|
|
||||||
;; Space separated.
|
|
||||||
;; Only these would be allowed if non-blank.
|
|
||||||
;; Example value: trusted.domain.org trusted.domain.net
|
|
||||||
;WHITELISTED_URIS =
|
|
||||||
;;
|
|
||||||
;; Forbidden URI patterns (POSIX regexp).
|
|
||||||
;; Space separated.
|
|
||||||
;; Only used if WHITELISTED_URIS is blank.
|
|
||||||
;; Example value: loadaverage.org/badguy stackexchange.com/.*spammer
|
|
||||||
;BLACKLISTED_URIS =
|
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;[oauth2_client]
|
;[oauth2_client]
|
||||||
|
|||||||
1
go.mod
1
go.mod
@@ -82,7 +82,6 @@ require (
|
|||||||
github.com/urfave/cli-docs/v3 v3.1.0
|
github.com/urfave/cli-docs/v3 v3.1.0
|
||||||
github.com/urfave/cli/v3 v3.14.0
|
github.com/urfave/cli/v3 v3.14.0
|
||||||
github.com/wneessen/go-mail v0.8.1
|
github.com/wneessen/go-mail v0.8.1
|
||||||
github.com/yohcop/openid-go v1.0.1
|
|
||||||
github.com/yuin/goldmark v1.8.6
|
github.com/yuin/goldmark v1.8.6
|
||||||
gitlab.com/gitlab-org/api/client-go/v3 v3.39.1
|
gitlab.com/gitlab-org/api/client-go/v3 v3.39.1
|
||||||
go.yaml.in/yaml/v4 v4.0.0
|
go.yaml.in/yaml/v4 v4.0.0
|
||||||
|
|||||||
16
go.sum
16
go.sum
@@ -550,10 +550,7 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
|||||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||||
github.com/yohcop/openid-go v1.0.1 h1:DPRd3iPO5F6O5zX2e62XpVAbPT6wV51cuucH0z9g3js=
|
|
||||||
github.com/yohcop/openid-go v1.0.1/go.mod h1:b/AvD03P0KHj4yuihb+VtLD6bYYgsy0zqBzPCRjkCNs=
|
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
|
||||||
github.com/yuin/goldmark v1.8.6 h1:d0VcaP1sx9GkFVkoW+KtggpGi2KZ965i14b0+bDQST4=
|
github.com/yuin/goldmark v1.8.6 h1:d0VcaP1sx9GkFVkoW+KtggpGi2KZ965i14b0+bDQST4=
|
||||||
github.com/yuin/goldmark v1.8.6/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
github.com/yuin/goldmark v1.8.6/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||||
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
|
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
|
||||||
@@ -592,7 +589,6 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
|||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
|
||||||
golang.org/x/crypto v0.58.0 h1:COkYLr4k7nDI4r6QtMML2AfzeIswWHAJthLS6K/M/54=
|
golang.org/x/crypto v0.58.0 h1:COkYLr4k7nDI4r6QtMML2AfzeIswWHAJthLS6K/M/54=
|
||||||
golang.org/x/crypto v0.58.0/go.mod h1:Xh+kl5A+M0gMiWDytH2Hhr5Z7WbNCpv/eB/EFgfSk+w=
|
golang.org/x/crypto v0.58.0/go.mod h1:Xh+kl5A+M0gMiWDytH2Hhr5Z7WbNCpv/eB/EFgfSk+w=
|
||||||
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM=
|
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM=
|
||||||
@@ -600,7 +596,6 @@ golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aI
|
|||||||
golang.org/x/image v0.47.0 h1:za/8gVz++PPZqmZJBNSISICN5Rrj5zixP/8K++fgmqo=
|
golang.org/x/image v0.47.0 h1:za/8gVz++PPZqmZJBNSISICN5Rrj5zixP/8K++fgmqo=
|
||||||
golang.org/x/image v0.47.0/go.mod h1:IkN5o4A7vq55vXN8FWjjnhg9Rs2GOtffqsSi9mWAKYE=
|
golang.org/x/image v0.47.0/go.mod h1:IkN5o4A7vq55vXN8FWjjnhg9Rs2GOtffqsSi9mWAKYE=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
|
||||||
golang.org/x/mod v0.42.0 h1:ICeL3t2L30ljSPHHxDnR4fqVcBt/zR2zJAb9juavmQg=
|
golang.org/x/mod v0.42.0 h1:ICeL3t2L30ljSPHHxDnR4fqVcBt/zR2zJAb9juavmQg=
|
||||||
golang.org/x/mod v0.42.0/go.mod h1:u5fzIrRInkL8MaO1cbRkUHT64v303YIT5KEqpNr7jRs=
|
golang.org/x/mod v0.42.0/go.mod h1:u5fzIrRInkL8MaO1cbRkUHT64v303YIT5KEqpNr7jRs=
|
||||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
@@ -609,8 +604,6 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
|
|||||||
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
|
||||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
|
||||||
golang.org/x/net v0.62.0 h1:18ELalfM5+XNRwzM2nYqXsZQ9ByS1uhj2O/U7+5jqRY=
|
golang.org/x/net v0.62.0 h1:18ELalfM5+XNRwzM2nYqXsZQ9ByS1uhj2O/U7+5jqRY=
|
||||||
golang.org/x/net v0.62.0/go.mod h1:WgDCOTH8iwtB66T4/GfI94AHo/hhdb7Hah9NDseQfiI=
|
golang.org/x/net v0.62.0/go.mod h1:WgDCOTH8iwtB66T4/GfI94AHo/hhdb7Hah9NDseQfiI=
|
||||||
golang.org/x/oauth2 v0.37.0 h1:JUlcxA8oAtauLfiH8FX2/FkAWHAdi0QtGCGc+hofE98=
|
golang.org/x/oauth2 v0.37.0 h1:JUlcxA8oAtauLfiH8FX2/FkAWHAdi0QtGCGc+hofE98=
|
||||||
@@ -619,7 +612,6 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.24.0 h1:iqiDrGoDgyuvAKrhLQthUEzU/R378N78gXbBjxed7oA=
|
golang.org/x/sync v0.24.0 h1:iqiDrGoDgyuvAKrhLQthUEzU/R378N78gXbBjxed7oA=
|
||||||
golang.org/x/sync v0.24.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
|
golang.org/x/sync v0.24.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@@ -633,24 +625,17 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.49.0 h1:XbzkgYJHdqh/8m2Uu0W/dQv8nktxx4BFHp1M0gROTXA=
|
golang.org/x/sys v0.49.0 h1:XbzkgYJHdqh/8m2Uu0W/dQv8nktxx4BFHp1M0gROTXA=
|
||||||
golang.org/x/sys v0.49.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
|
golang.org/x/sys v0.49.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
|
||||||
golang.org/x/term v0.47.0 h1:cpyOwQkr2Nzxe2p6KnrTG7Ei2wtzjsuSjhpXXepZ/BM=
|
golang.org/x/term v0.47.0 h1:cpyOwQkr2Nzxe2p6KnrTG7Ei2wtzjsuSjhpXXepZ/BM=
|
||||||
golang.org/x/term v0.47.0/go.mod h1:efAfAuG6RKABvg1ArGebRkvX6LXul14SM67o2e4mUCY=
|
golang.org/x/term v0.47.0/go.mod h1:efAfAuG6RKABvg1ArGebRkvX6LXul14SM67o2e4mUCY=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
|
||||||
golang.org/x/text v0.43.0 h1:1QivrlhwAsnMOcqgOdl9my7s9OyZl8O5/UGUTJVXoWc=
|
golang.org/x/text v0.43.0 h1:1QivrlhwAsnMOcqgOdl9my7s9OyZl8O5/UGUTJVXoWc=
|
||||||
golang.org/x/text v0.43.0/go.mod h1:hbSIYA/amXcRXTFSZqh8tWHEoqRvvbKPJ2uz/KU+a+s=
|
golang.org/x/text v0.43.0/go.mod h1:hbSIYA/amXcRXTFSZqh8tWHEoqRvvbKPJ2uz/KU+a+s=
|
||||||
golang.org/x/time v0.16.0 h1:vMb6ptszcQMkcwiRTAuNNU50gom6++Q/6gY2hDM6VDE=
|
golang.org/x/time v0.16.0 h1:vMb6ptszcQMkcwiRTAuNNU50gom6++Q/6gY2hDM6VDE=
|
||||||
@@ -658,7 +643,6 @@ golang.org/x/time v0.16.0/go.mod h1:rVKOqvZeKvrDKTQiAHJ7wmwP0RzleSphoEA9RcdLA0s=
|
|||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
|
||||||
golang.org/x/tools v0.52.0 h1:ryxg4U63oaUYk7uOLTF37n43f4qgtGvqV4Ioit1BaEs=
|
golang.org/x/tools v0.52.0 h1:ryxg4U63oaUYk7uOLTF37n43f4qgtGvqV4Ioit1BaEs=
|
||||||
golang.org/x/tools v0.52.0/go.mod h1:ItcP5UNaVGG+Yc4eIYmaFLNJ4soEopkx5Y2d4lcEurs=
|
golang.org/x/tools v0.52.0/go.mod h1:ItcP5UNaVGG+Yc4eIYmaFLNJ4soEopkx5Y2d4lcEurs=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
|||||||
@@ -442,6 +442,7 @@ func prepareMigrationTasks() []*migration {
|
|||||||
newMigration(363, "Add release_id to reaction table", v29.AddReleaseIDToReaction),
|
newMigration(363, "Add release_id to reaction table", v29.AddReleaseIDToReaction),
|
||||||
newMigration(364, "Add new table project_workflow", v29.AddProjectWorkflow),
|
newMigration(364, "Add new table project_workflow", v29.AddProjectWorkflow),
|
||||||
newMigration(365, "Normalize legacy repository unit and login source JSON", v29.NormalizeLegacyConfigJSON),
|
newMigration(365, "Normalize legacy repository unit and login source JSON", v29.NormalizeLegacyConfigJSON),
|
||||||
|
newMigration(366, "Drop user_open_id table", v29.DropUserOpenIDTable),
|
||||||
}
|
}
|
||||||
return preparedMigrations
|
return preparedMigrations
|
||||||
}
|
}
|
||||||
|
|||||||
14
modelmigration/v29/v366.go
Normal file
14
modelmigration/v29/v366.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package v29
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"gitea.dev/modelmigration/base"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DropUserOpenIDTable(_ctx context.Context, x base.EngineMigration) error {
|
||||||
|
return x.DropTables("user_open_id")
|
||||||
|
}
|
||||||
33
modelmigration/v29/v366_test.go
Normal file
33
modelmigration/v29/v366_test.go
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package v29
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gitea.dev/modelmigration/migrationtest"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDropUserOpenIDTable(t *testing.T) {
|
||||||
|
type UserOpenID struct {
|
||||||
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
x, cleanup := migrationtest.PrepareTestEnv(t, 0, new(UserOpenID))
|
||||||
|
defer cleanup()
|
||||||
|
if x == nil || t.Failed() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
exists, err := x.IsTableExist("user_open_id")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, exists)
|
||||||
|
require.NoError(t, DropUserOpenIDTable(t.Context(), x))
|
||||||
|
exists, err = x.IsTableExist("user_open_id")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.False(t, exists)
|
||||||
|
require.NoError(t, DropUserOpenIDTable(t.Context(), x))
|
||||||
|
}
|
||||||
@@ -88,8 +88,6 @@ var (
|
|||||||
UserWebAuthRemove = define("user:webauth:remove", "Removed WebAuthn key {credential} from user {scope}.")
|
UserWebAuthRemove = define("user:webauth:remove", "Removed WebAuthn key {credential} from user {scope}.")
|
||||||
UserExternalLoginAdd = define("user:externallogin:add", "Added external login {external_id} for user {scope} using provider {provider}.")
|
UserExternalLoginAdd = define("user:externallogin:add", "Added external login {external_id} for user {scope} using provider {provider}.")
|
||||||
UserExternalLoginRemove = define("user:externallogin:remove", "Removed external login from authentication source {auth_source_id} for user {scope}.")
|
UserExternalLoginRemove = define("user:externallogin:remove", "Removed external login from authentication source {auth_source_id} for user {scope}.")
|
||||||
UserOpenIDAdd = define("user:openid:add", "Associated OpenID {openid} to user {scope}.")
|
|
||||||
UserOpenIDRemove = define("user:openid:remove", "Removed OpenID {openid} from user {scope}.")
|
|
||||||
UserAccessTokenAdd = define("user:accesstoken:add", "Added access token {token} for user {scope} with scope {token_scope}.")
|
UserAccessTokenAdd = define("user:accesstoken:add", "Added access token {token} for user {scope} with scope {token_scope}.")
|
||||||
UserAccessTokenRegenerate = define("user:accesstoken:regenerate", "Regenerated access token {token} of user {scope}.")
|
UserAccessTokenRegenerate = define("user:accesstoken:regenerate", "Regenerated access token {token} of user {scope}.")
|
||||||
UserAccessTokenRemove = define("user:accesstoken:remove", "Removed access token {token} from user {scope}.")
|
UserAccessTokenRemove = define("user:accesstoken:remove", "Removed access token {token} from user {scope}.")
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
-
|
|
||||||
id: 1
|
|
||||||
uid: 1
|
|
||||||
uri: https://user1.domain1.tld/
|
|
||||||
show: false
|
|
||||||
|
|
||||||
-
|
|
||||||
id: 2
|
|
||||||
uid: 1
|
|
||||||
uri: http://user1.domain2.tld/
|
|
||||||
show: true
|
|
||||||
|
|
||||||
-
|
|
||||||
id: 3
|
|
||||||
uid: 2
|
|
||||||
uri: https://domain1.tld/user2/
|
|
||||||
show: true
|
|
||||||
|
|
||||||
# DO NOT add more test data in the fixtures, test case should prepare their own test data separately and clearly
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package user
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"gitea.dev/models/db"
|
|
||||||
"gitea.dev/modules/util"
|
|
||||||
|
|
||||||
"xorm.io/builder"
|
|
||||||
)
|
|
||||||
|
|
||||||
// UserOpenID is the list of all OpenID identities of a user.
|
|
||||||
// Since this is a middle table, name it OpenID is not suitable, so we ignore the lint here
|
|
||||||
type UserOpenID struct { //revive:disable-line:exported
|
|
||||||
ID int64 `xorm:"pk autoincr"`
|
|
||||||
UID int64 `xorm:"INDEX NOT NULL"`
|
|
||||||
URI string `xorm:"UNIQUE NOT NULL"`
|
|
||||||
Show bool `xorm:"DEFAULT false"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
db.RegisterModel(new(UserOpenID))
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUserOpenIDs returns all openid addresses that belongs to given user.
|
|
||||||
func GetUserOpenIDs(ctx context.Context, uid int64) ([]*UserOpenID, error) {
|
|
||||||
openids := make([]*UserOpenID, 0, 5)
|
|
||||||
if err := db.GetEngine(ctx).
|
|
||||||
Where("uid=?", uid).
|
|
||||||
Asc("id").
|
|
||||||
Find(&openids); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return openids, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// isOpenIDUsed returns true if the openid has been used.
|
|
||||||
func isOpenIDUsed(ctx context.Context, uri string) (bool, error) {
|
|
||||||
if len(uri) == 0 {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return db.Exist[UserOpenID](ctx, builder.Eq{"uri": uri})
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrOpenIDAlreadyUsed represents a "OpenIDAlreadyUsed" kind of error.
|
|
||||||
type ErrOpenIDAlreadyUsed struct {
|
|
||||||
OpenID string
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsErrOpenIDAlreadyUsed checks if an error is a ErrOpenIDAlreadyUsed.
|
|
||||||
func IsErrOpenIDAlreadyUsed(err error) bool {
|
|
||||||
_, ok := err.(ErrOpenIDAlreadyUsed)
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err ErrOpenIDAlreadyUsed) Error() string {
|
|
||||||
return fmt.Sprintf("OpenID already in use [oid: %s]", err.OpenID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err ErrOpenIDAlreadyUsed) Unwrap() error {
|
|
||||||
return util.ErrAlreadyExist
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddUserOpenID adds an pre-verified/normalized OpenID URI to given user.
|
|
||||||
// NOTE: make sure openid.URI is normalized already
|
|
||||||
func AddUserOpenID(ctx context.Context, openid *UserOpenID) error {
|
|
||||||
used, err := isOpenIDUsed(ctx, openid.URI)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
} else if used {
|
|
||||||
return ErrOpenIDAlreadyUsed{openid.URI}
|
|
||||||
}
|
|
||||||
|
|
||||||
return db.Insert(ctx, openid)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUserOpenIDByID returns the OpenID with the given ID owned by uid.
|
|
||||||
func GetUserOpenIDByID(ctx context.Context, id, uid int64) (*UserOpenID, error) {
|
|
||||||
oid, has, err := db.Get[UserOpenID](ctx, builder.Eq{"id": id, "uid": uid})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if !has {
|
|
||||||
return nil, util.NewNotExistErrorf("OpenID is unknown")
|
|
||||||
}
|
|
||||||
return oid, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteUserOpenID deletes an openid address of given user.
|
|
||||||
func DeleteUserOpenID(ctx context.Context, openid *UserOpenID) (err error) {
|
|
||||||
var deleted int64
|
|
||||||
// ask to check UID
|
|
||||||
address := UserOpenID{
|
|
||||||
UID: openid.UID,
|
|
||||||
}
|
|
||||||
if openid.ID > 0 {
|
|
||||||
deleted, err = db.GetEngine(ctx).ID(openid.ID).Delete(&address)
|
|
||||||
} else {
|
|
||||||
deleted, err = db.GetEngine(ctx).
|
|
||||||
Where("openid=?", openid.URI).
|
|
||||||
Delete(&address)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
} else if deleted != 1 {
|
|
||||||
return util.NewNotExistErrorf("OpenID is unknown")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToggleUserOpenIDVisibility toggles visibility of an openid address of given user.
|
|
||||||
func ToggleUserOpenIDVisibility(ctx context.Context, id int64, user *User) error {
|
|
||||||
affected, err := db.GetEngine(ctx).Exec("update `user_open_id` set `show` = not `show` where `id` = ? AND uid = ?", id, user.ID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if n, _ := affected.RowsAffected(); n != 1 {
|
|
||||||
return util.NewNotExistErrorf("OpenID is unknown")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package user_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"gitea.dev/models/unittest"
|
|
||||||
user_model "gitea.dev/models/user"
|
|
||||||
"gitea.dev/modules/util"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestUserOpenIDs(t *testing.T) {
|
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
|
||||||
|
|
||||||
t.Run("GetUserOpenIDs", func(t *testing.T) {
|
|
||||||
oids, err := user_model.GetUserOpenIDs(t.Context(), int64(1))
|
|
||||||
if assert.NoError(t, err) && assert.Len(t, oids, 2) {
|
|
||||||
assert.Equal(t, "https://user1.domain1.tld/", oids[0].URI)
|
|
||||||
assert.False(t, oids[0].Show)
|
|
||||||
assert.Equal(t, "http://user1.domain2.tld/", oids[1].URI)
|
|
||||||
assert.True(t, oids[1].Show)
|
|
||||||
}
|
|
||||||
|
|
||||||
oids, err = user_model.GetUserOpenIDs(t.Context(), int64(2))
|
|
||||||
if assert.NoError(t, err) && assert.Len(t, oids, 1) {
|
|
||||||
assert.Equal(t, "https://domain1.tld/user2/", oids[0].URI)
|
|
||||||
assert.True(t, oids[0].Show)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("ToggleUserOpenIDVisibilityRequiresOwnership", func(t *testing.T) {
|
|
||||||
unauthorizedUser, err := user_model.GetUserByID(t.Context(), int64(2))
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = user_model.ToggleUserOpenIDVisibility(t.Context(), int64(1), unauthorizedUser)
|
|
||||||
require.Error(t, err)
|
|
||||||
assert.ErrorIs(t, err, util.ErrNotExist)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("ToggleUserOpenIDVisibility", func(t *testing.T) {
|
|
||||||
user, err := user_model.GetUserByID(t.Context(), int64(2))
|
|
||||||
require.NoError(t, err)
|
|
||||||
oids, err := user_model.GetUserOpenIDs(t.Context(), int64(2))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Len(t, oids, 1)
|
|
||||||
assert.True(t, oids[0].Show)
|
|
||||||
|
|
||||||
err = user_model.ToggleUserOpenIDVisibility(t.Context(), oids[0].ID, user)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
oids, err = user_model.GetUserOpenIDs(t.Context(), int64(2))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Len(t, oids, 1)
|
|
||||||
|
|
||||||
assert.False(t, oids[0].Show)
|
|
||||||
err = user_model.ToggleUserOpenIDVisibility(t.Context(), oids[0].ID, user)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
oids, err = user_model.GetUserOpenIDs(t.Context(), int64(2))
|
|
||||||
require.NoError(t, err)
|
|
||||||
if assert.Len(t, oids, 1) {
|
|
||||||
assert.True(t, oids[0].Show)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -22,7 +22,6 @@ import (
|
|||||||
|
|
||||||
"gitea.dev/models/auth"
|
"gitea.dev/models/auth"
|
||||||
"gitea.dev/models/db"
|
"gitea.dev/models/db"
|
||||||
"gitea.dev/modules/auth/openid"
|
|
||||||
"gitea.dev/modules/auth/password/hash"
|
"gitea.dev/modules/auth/password/hash"
|
||||||
"gitea.dev/modules/base"
|
"gitea.dev/modules/base"
|
||||||
"gitea.dev/modules/container"
|
"gitea.dev/modules/container"
|
||||||
@@ -1441,32 +1440,6 @@ func GetIndividualUserByLoginSource(ctx context.Context, loginType auth.Type, lo
|
|||||||
return user, has, err
|
return user, has, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserByOpenID returns the user object by given OpenID if exists.
|
|
||||||
func GetUserByOpenID(ctx context.Context, uri string) (*User, error) {
|
|
||||||
if len(uri) == 0 {
|
|
||||||
return nil, ErrUserNotExist{Name: uri}
|
|
||||||
}
|
|
||||||
|
|
||||||
uri, err := openid.Normalize(uri)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Trace("Normalized OpenID URI: " + uri)
|
|
||||||
|
|
||||||
// Otherwise, check in openid table
|
|
||||||
oid := &UserOpenID{}
|
|
||||||
has, err := db.GetEngine(ctx).Where("uri=?", uri).Get(oid)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if has {
|
|
||||||
return GetUserByID(ctx, oid.UID)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, ErrUserNotExist{Name: uri}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAdminUser returns the first administrator
|
// GetAdminUser returns the first administrator
|
||||||
func GetAdminUser(ctx context.Context) (*User, error) {
|
func GetAdminUser(ctx context.Context) (*User, error) {
|
||||||
var admin User
|
var admin User
|
||||||
|
|||||||
@@ -417,25 +417,6 @@ func TestNewUserRedirect(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetUserByOpenID(t *testing.T) {
|
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
|
||||||
|
|
||||||
_, err := user_model.GetUserByOpenID(t.Context(), "https://unknown")
|
|
||||||
if assert.Error(t, err) {
|
|
||||||
assert.True(t, user_model.IsErrUserNotExist(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
user, err := user_model.GetUserByOpenID(t.Context(), "https://user1.domain1.tld")
|
|
||||||
if assert.NoError(t, err) {
|
|
||||||
assert.Equal(t, int64(1), user.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
user, err = user_model.GetUserByOpenID(t.Context(), "https://domain1.tld/user2/")
|
|
||||||
if assert.NoError(t, err) {
|
|
||||||
assert.Equal(t, int64(2), user.ID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFollowUser(t *testing.T) {
|
func TestFollowUser(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package openid
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/yohcop/openid-go"
|
|
||||||
)
|
|
||||||
|
|
||||||
type timedDiscoveredInfo struct {
|
|
||||||
info openid.DiscoveredInfo
|
|
||||||
time time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
type timedDiscoveryCache struct {
|
|
||||||
cache map[string]timedDiscoveredInfo
|
|
||||||
ttl time.Duration
|
|
||||||
mutex *sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTimedDiscoveryCache(ttl time.Duration) *timedDiscoveryCache {
|
|
||||||
return &timedDiscoveryCache{cache: map[string]timedDiscoveredInfo{}, ttl: ttl, mutex: &sync.Mutex{}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *timedDiscoveryCache) Put(id string, info openid.DiscoveredInfo) {
|
|
||||||
s.mutex.Lock()
|
|
||||||
defer s.mutex.Unlock()
|
|
||||||
|
|
||||||
s.cache[id] = timedDiscoveredInfo{info: info, time: time.Now()}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete timed-out cache entries
|
|
||||||
func (s *timedDiscoveryCache) cleanTimedOut() {
|
|
||||||
now := time.Now()
|
|
||||||
for k, e := range s.cache {
|
|
||||||
diff := now.Sub(e.time)
|
|
||||||
if diff > s.ttl {
|
|
||||||
delete(s.cache, k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *timedDiscoveryCache) Get(id string) openid.DiscoveredInfo {
|
|
||||||
s.mutex.Lock()
|
|
||||||
defer s.mutex.Unlock()
|
|
||||||
|
|
||||||
// Delete old cached while we are at it.
|
|
||||||
s.cleanTimedOut()
|
|
||||||
|
|
||||||
if info, has := s.cache[id]; has {
|
|
||||||
return info.info
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package openid
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
type testDiscoveredInfo struct{}
|
|
||||||
|
|
||||||
func (s *testDiscoveredInfo) ClaimedID() string {
|
|
||||||
return "claimedID"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *testDiscoveredInfo) OpEndpoint() string {
|
|
||||||
return "opEndpoint"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *testDiscoveredInfo) OpLocalID() string {
|
|
||||||
return "opLocalID"
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTimedDiscoveryCache(t *testing.T) {
|
|
||||||
ttl := 50 * time.Millisecond
|
|
||||||
dc := newTimedDiscoveryCache(ttl)
|
|
||||||
|
|
||||||
// Put some initial values
|
|
||||||
dc.Put("foo", &testDiscoveredInfo{}) // openid.opEndpoint: "a", openid.opLocalID: "b", openid.claimedID: "c"})
|
|
||||||
|
|
||||||
// Make sure we can retrieve them
|
|
||||||
di := dc.Get("foo")
|
|
||||||
require.NotNil(t, di)
|
|
||||||
assert.Equal(t, "opEndpoint", di.OpEndpoint())
|
|
||||||
assert.Equal(t, "opLocalID", di.OpLocalID())
|
|
||||||
assert.Equal(t, "claimedID", di.ClaimedID())
|
|
||||||
|
|
||||||
// Attempt to get a non-existent value
|
|
||||||
assert.Nil(t, dc.Get("bar"))
|
|
||||||
|
|
||||||
// Sleep for a while and try to retrieve again
|
|
||||||
time.Sleep(ttl * 3 / 2)
|
|
||||||
|
|
||||||
assert.Nil(t, dc.Get("foo"))
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package openid
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"gitea.dev/modules/egress"
|
|
||||||
|
|
||||||
"github.com/yohcop/openid-go"
|
|
||||||
)
|
|
||||||
|
|
||||||
// For the demo, we use in-memory infinite storage nonce and discovery
|
|
||||||
// cache. In your app, do not use this as it will eat up memory and
|
|
||||||
// never
|
|
||||||
// free it. Use your own implementation, on a better database system.
|
|
||||||
// If you have multiple servers for example, you may need to share at
|
|
||||||
// least
|
|
||||||
// the nonceStore between them.
|
|
||||||
var (
|
|
||||||
nonceStore = openid.NewSimpleNonceStore()
|
|
||||||
discoveryCache = newTimedDiscoveryCache(24 * time.Hour)
|
|
||||||
|
|
||||||
// openIDInstance keeps user-supplied OpenID identifiers within [security] ALLOWED_HOST_LIST
|
|
||||||
openIDInstance = sync.OnceValue(func() *openid.OpenID {
|
|
||||||
return openid.NewOpenID(&http.Client{
|
|
||||||
Timeout: 30 * time.Second,
|
|
||||||
Transport: egress.NewSecurityPolicy("openid").NewHTTPTransport(),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
// Verify handles response from OpenID provider
|
|
||||||
func Verify(fullURL string) (id string, err error) {
|
|
||||||
return openIDInstance().Verify(fullURL, discoveryCache, nonceStore)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Normalize normalizes an OpenID URI
|
|
||||||
func Normalize(url string) (id string, err error) {
|
|
||||||
return openid.Normalize(url)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RedirectURL redirects browser
|
|
||||||
func RedirectURL(id, callbackURL, realm string) (string, error) {
|
|
||||||
return openIDInstance().RedirectURL(id, callbackURL, realm)
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package openid
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"sync/atomic"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestOpenIDDiscoveryBlocksInternalHost(t *testing.T) {
|
|
||||||
var reached atomic.Bool
|
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
reached.Store(true)
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
}))
|
|
||||||
defer srv.Close()
|
|
||||||
|
|
||||||
// RedirectURL performs server-side discovery of the identifier URL; a loopback URL
|
|
||||||
// must be refused at dial time instead of reaching the internal server
|
|
||||||
_, err := RedirectURL(srv.URL, "http://example.com/callback", "http://example.com/")
|
|
||||||
require.Error(t, err)
|
|
||||||
assert.False(t, reached.Load(), "OpenID discovery must not reach an internal/loopback host")
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,6 @@
|
|||||||
package setting
|
package setting
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"regexp"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -82,12 +81,6 @@ var Service = struct {
|
|||||||
UserDeleteWithCommentsMaxTime time.Duration
|
UserDeleteWithCommentsMaxTime time.Duration
|
||||||
ValidSiteURLSchemes []string
|
ValidSiteURLSchemes []string
|
||||||
|
|
||||||
// OpenID settings
|
|
||||||
EnableOpenIDSignIn bool
|
|
||||||
EnableOpenIDSignUp bool
|
|
||||||
OpenIDWhitelist []*regexp.Regexp
|
|
||||||
OpenIDBlacklist []*regexp.Regexp
|
|
||||||
|
|
||||||
// Explore page settings
|
// Explore page settings
|
||||||
Explore struct {
|
Explore struct {
|
||||||
RequireSigninView bool `ini:"REQUIRE_SIGNIN_VIEW"`
|
RequireSigninView bool `ini:"REQUIRE_SIGNIN_VIEW"`
|
||||||
@@ -257,30 +250,9 @@ func loadServiceFrom(rootCfg ConfigProvider) {
|
|||||||
|
|
||||||
mustMapSetting(rootCfg, "service.explore", &Service.Explore)
|
mustMapSetting(rootCfg, "service.explore", &Service.Explore)
|
||||||
|
|
||||||
loadOpenIDSetting(rootCfg)
|
|
||||||
loadQosSetting(rootCfg)
|
loadQosSetting(rootCfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadOpenIDSetting(rootCfg ConfigProvider) {
|
|
||||||
sec := rootCfg.Section("openid")
|
|
||||||
Service.EnableOpenIDSignIn = sec.Key("ENABLE_OPENID_SIGNIN").MustBool(false)
|
|
||||||
Service.EnableOpenIDSignUp = sec.Key("ENABLE_OPENID_SIGNUP").MustBool(false)
|
|
||||||
pats := sec.Key("WHITELISTED_URIS").Strings(" ")
|
|
||||||
if len(pats) != 0 {
|
|
||||||
Service.OpenIDWhitelist = make([]*regexp.Regexp, len(pats))
|
|
||||||
for i, p := range pats {
|
|
||||||
Service.OpenIDWhitelist[i] = regexp.MustCompilePOSIX(p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pats = sec.Key("BLACKLISTED_URIS").Strings(" ")
|
|
||||||
if len(pats) != 0 {
|
|
||||||
Service.OpenIDBlacklist = make([]*regexp.Regexp, len(pats))
|
|
||||||
for i, p := range pats {
|
|
||||||
Service.OpenIDBlacklist[i] = regexp.MustCompilePOSIX(p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadQosSetting(rootCfg ConfigProvider) {
|
func loadQosSetting(rootCfg ConfigProvider) {
|
||||||
sec := rootCfg.Section("qos")
|
sec := rootCfg.Section("qos")
|
||||||
Service.QoS.Enabled = sec.Key("ENABLED").MustBool(false)
|
Service.QoS.Enabled = sec.Key("ENABLED").MustBool(false)
|
||||||
|
|||||||
@@ -36,9 +36,8 @@ func CommonTemplateContextData() reqctx.ContextData {
|
|||||||
"ShowFooterVersion": setting.Other.ShowFooterVersion,
|
"ShowFooterVersion": setting.Other.ShowFooterVersion,
|
||||||
"DisableDownloadSourceArchives": setting.Repository.DisableDownloadSourceArchives,
|
"DisableDownloadSourceArchives": setting.Repository.DisableDownloadSourceArchives,
|
||||||
|
|
||||||
"EnableSwagger": setting.API.EnableSwagger,
|
"EnableSwagger": setting.API.EnableSwagger,
|
||||||
"EnableOpenIDSignIn": setting.Service.EnableOpenIDSignIn,
|
"PageStartTime": time.Now(),
|
||||||
"PageStartTime": time.Now(),
|
|
||||||
|
|
||||||
"RunModeIsProd": setting.IsProd,
|
"RunModeIsProd": setting.IsProd,
|
||||||
"ViteModeIsDev": public.IsViteDevMode(),
|
"ViteModeIsDev": public.IsViteDevMode(),
|
||||||
|
|||||||
@@ -409,7 +409,6 @@
|
|||||||
"auth.twofa_scratch_token_incorrect": "Your scratch code is incorrect.",
|
"auth.twofa_scratch_token_incorrect": "Your scratch code is incorrect.",
|
||||||
"auth.twofa_required": "You must set up two-factor authentication to get access to repositories, or try to log in again.",
|
"auth.twofa_required": "You must set up two-factor authentication to get access to repositories, or try to log in again.",
|
||||||
"auth.login_userpass": "Sign In",
|
"auth.login_userpass": "Sign In",
|
||||||
"auth.login_openid": "OpenID",
|
|
||||||
"auth.oauth_signup_tab": "Register New Account",
|
"auth.oauth_signup_tab": "Register New Account",
|
||||||
"auth.oauth_signup_title": "Complete New Account",
|
"auth.oauth_signup_title": "Complete New Account",
|
||||||
"auth.oauth_signup_submit": "Complete Account",
|
"auth.oauth_signup_submit": "Complete Account",
|
||||||
@@ -420,12 +419,6 @@
|
|||||||
"auth.oauth.signin.error.access_denied": "The authorization request was denied.",
|
"auth.oauth.signin.error.access_denied": "The authorization request was denied.",
|
||||||
"auth.oauth.signin.error.temporarily_unavailable": "Authorization failed because the authentication server is temporarily unavailable. Please try again later.",
|
"auth.oauth.signin.error.temporarily_unavailable": "Authorization failed because the authentication server is temporarily unavailable. Please try again later.",
|
||||||
"auth.oauth_callback_unable_auto_reg": "Auto Registration is enabled, but OAuth2 Provider %[1]s returned missing fields: %[2]s, unable to create an account automatically. Please create or link to an account, or contact the site administrator.",
|
"auth.oauth_callback_unable_auto_reg": "Auto Registration is enabled, but OAuth2 Provider %[1]s returned missing fields: %[2]s, unable to create an account automatically. Please create or link to an account, or contact the site administrator.",
|
||||||
"auth.openid_connect_submit": "Connect",
|
|
||||||
"auth.openid_connect_title": "Connect to an existing account",
|
|
||||||
"auth.openid_connect_desc": "The chosen OpenID URI is unknown. Associate it with a new account here.",
|
|
||||||
"auth.openid_register_title": "Create new account",
|
|
||||||
"auth.openid_register_desc": "The chosen OpenID URI is unknown. Associate it with a new account here.",
|
|
||||||
"auth.openid_signin_desc": "Enter your OpenID URI. For example: alice.openid.example.org or https://openid.example.org/alice.",
|
|
||||||
"auth.disable_forgot_password_mail": "Account recovery is disabled because no email address is set up. Please contact your site administrator.",
|
"auth.disable_forgot_password_mail": "Account recovery is disabled because no email address is set up. Please contact your site administrator.",
|
||||||
"auth.disable_forgot_password_mail_admin": "Account recovery is only available when an email address is set up.",
|
"auth.disable_forgot_password_mail_admin": "Account recovery is only available when an email address is set up.",
|
||||||
"auth.email_domain_blacklisted": "You cannot register with your email address.",
|
"auth.email_domain_blacklisted": "You cannot register with your email address.",
|
||||||
@@ -443,7 +436,6 @@
|
|||||||
"auth.password_pwned_err": "Could not complete request to HaveIBeenPwned",
|
"auth.password_pwned_err": "Could not complete request to HaveIBeenPwned",
|
||||||
"auth.last_admin": "You cannot remove the last admin. There must be at least one admin.",
|
"auth.last_admin": "You cannot remove the last admin. There must be at least one admin.",
|
||||||
"auth.signin_passkey": "Sign in with a passkey",
|
"auth.signin_passkey": "Sign in with a passkey",
|
||||||
"auth.back_to_sign_in": "Back to Sign In",
|
|
||||||
"mail.view_it_on": "View it on %s",
|
"mail.view_it_on": "View it on %s",
|
||||||
"mail.reply": "or reply to this email directly",
|
"mail.reply": "or reply to this email directly",
|
||||||
"mail.reply_directly": "Reply to this email directly",
|
"mail.reply_directly": "Reply to this email directly",
|
||||||
@@ -563,7 +555,6 @@
|
|||||||
"form.email_been_used": "The email address is already used.",
|
"form.email_been_used": "The email address is already used.",
|
||||||
"form.email_invalid": "The email address is invalid.",
|
"form.email_invalid": "The email address is invalid.",
|
||||||
"form.email_domain_is_not_allowed": "The domain of user email address <b>%s</b> conflicts with EMAIL_DOMAIN_ALLOWLIST or EMAIL_DOMAIN_BLOCKLIST. Please ensure your operation is expected.",
|
"form.email_domain_is_not_allowed": "The domain of user email address <b>%s</b> conflicts with EMAIL_DOMAIN_ALLOWLIST or EMAIL_DOMAIN_BLOCKLIST. Please ensure your operation is expected.",
|
||||||
"form.openid_been_used": "The OpenID address \"%s\" is already used.",
|
|
||||||
"form.username_password_incorrect": "Username or password is incorrect.",
|
"form.username_password_incorrect": "Username or password is incorrect.",
|
||||||
"form.password_complexity": "Password does not pass complexity requirements:",
|
"form.password_complexity": "Password does not pass complexity requirements:",
|
||||||
"form.password_lowercase_one": "At least one lowercase character",
|
"form.password_lowercase_one": "At least one lowercase character",
|
||||||
@@ -717,7 +708,6 @@
|
|||||||
"settings.emails": "Email Addresses",
|
"settings.emails": "Email Addresses",
|
||||||
"settings.manage_emails": "Manage Email Addresses",
|
"settings.manage_emails": "Manage Email Addresses",
|
||||||
"settings.manage_themes": "Select default theme",
|
"settings.manage_themes": "Select default theme",
|
||||||
"settings.manage_openid": "Manage OpenID Addresses",
|
|
||||||
"settings.email_desc": "Your primary email address will be used for notifications, password recovery and, provided that it is not hidden, web-based Git operations.",
|
"settings.email_desc": "Your primary email address will be used for notifications, password recovery and, provided that it is not hidden, web-based Git operations.",
|
||||||
"settings.theme_desc": "This will be your default theme across the site.",
|
"settings.theme_desc": "This will be your default theme across the site.",
|
||||||
"settings.theme_colorblindness_help": "Color blindness Theme Support",
|
"settings.theme_colorblindness_help": "Color blindness Theme Support",
|
||||||
@@ -735,21 +725,14 @@
|
|||||||
"settings.email_deletion_success": "The email address has been removed.",
|
"settings.email_deletion_success": "The email address has been removed.",
|
||||||
"settings.theme_update_success": "Your theme was updated.",
|
"settings.theme_update_success": "Your theme was updated.",
|
||||||
"settings.theme_update_error": "The selected theme does not exist.",
|
"settings.theme_update_error": "The selected theme does not exist.",
|
||||||
"settings.openid_deletion": "Remove OpenID Address",
|
|
||||||
"settings.openid_deletion_desc": "Removing this OpenID address from your account will prevent you from signing in with it. Continue?",
|
|
||||||
"settings.openid_deletion_success": "The OpenID address has been removed.",
|
|
||||||
"settings.add_new_email": "Add New Email Address",
|
"settings.add_new_email": "Add New Email Address",
|
||||||
"settings.add_new_openid": "Add New OpenID URI",
|
|
||||||
"settings.add_email": "Add Email Address",
|
"settings.add_email": "Add Email Address",
|
||||||
"settings.add_openid": "Add OpenID URI",
|
|
||||||
"settings.add_email_confirmation_sent": "A confirmation email has been sent to \"%s\". Please check your inbox within the next %s to confirm your email address.",
|
"settings.add_email_confirmation_sent": "A confirmation email has been sent to \"%s\". Please check your inbox within the next %s to confirm your email address.",
|
||||||
"settings.email_primary_not_found": "The selected email address could not be found.",
|
"settings.email_primary_not_found": "The selected email address could not be found.",
|
||||||
"settings.add_email_success": "The new email address has been added.",
|
"settings.add_email_success": "The new email address has been added.",
|
||||||
"settings.email_preference_set_success": "Email preference has been set successfully.",
|
"settings.email_preference_set_success": "Email preference has been set successfully.",
|
||||||
"settings.add_openid_success": "The new OpenID address has been added.",
|
|
||||||
"settings.keep_email_private": "Hide Email Address",
|
"settings.keep_email_private": "Hide Email Address",
|
||||||
"settings.keep_email_private_popup": "This will hide your email address from your profile, as well as when you make a pull request or edit a file using the web interface. Pushed commits will not be modified. Use %s in commits to associate them with your account.",
|
"settings.keep_email_private_popup": "This will hide your email address from your profile, as well as when you make a pull request or edit a file using the web interface. Pushed commits will not be modified. Use %s in commits to associate them with your account.",
|
||||||
"settings.openid_desc": "OpenID lets you delegate authentication to an external provider.",
|
|
||||||
"settings.manage_ssh_keys": "Manage SSH Keys",
|
"settings.manage_ssh_keys": "Manage SSH Keys",
|
||||||
"settings.manage_ssh_principals": "Manage SSH Certificate Principals",
|
"settings.manage_ssh_principals": "Manage SSH Certificate Principals",
|
||||||
"settings.manage_gpg_keys": "Manage GPG Keys",
|
"settings.manage_gpg_keys": "Manage GPG Keys",
|
||||||
@@ -818,8 +801,6 @@
|
|||||||
"settings.key_state_desc": "This key has been used in the last 7 days",
|
"settings.key_state_desc": "This key has been used in the last 7 days",
|
||||||
"settings.token_state_desc": "This token has been used in the last 7 days",
|
"settings.token_state_desc": "This token has been used in the last 7 days",
|
||||||
"settings.principal_state_desc": "This principal has been used in the last 7 days",
|
"settings.principal_state_desc": "This principal has been used in the last 7 days",
|
||||||
"settings.show_openid": "Show on profile",
|
|
||||||
"settings.hide_openid": "Hide from profile",
|
|
||||||
"settings.ssh_disabled": "SSH Disabled",
|
"settings.ssh_disabled": "SSH Disabled",
|
||||||
"settings.ssh_signonly": "SSH is currently disabled so these keys are only used for commit signature verification.",
|
"settings.ssh_signonly": "SSH is currently disabled so these keys are only used for commit signature verification.",
|
||||||
"settings.ssh_externally_managed": "This SSH key is externally managed for this user",
|
"settings.ssh_externally_managed": "This SSH key is externally managed for this user",
|
||||||
@@ -3358,8 +3339,6 @@
|
|||||||
"admin.config.disable_register": "Disable Self-Registration",
|
"admin.config.disable_register": "Disable Self-Registration",
|
||||||
"admin.config.allow_only_internal_registration": "Allow Registration Only Through Gitea itself",
|
"admin.config.allow_only_internal_registration": "Allow Registration Only Through Gitea itself",
|
||||||
"admin.config.allow_only_external_registration": "Allow Registration Only Through External Services",
|
"admin.config.allow_only_external_registration": "Allow Registration Only Through External Services",
|
||||||
"admin.config.enable_openid_signup": "Enable OpenID Self-Registration",
|
|
||||||
"admin.config.enable_openid_signin": "Enable OpenID Sign-In",
|
|
||||||
"admin.config.show_registration_button": "Show Register Button",
|
"admin.config.show_registration_button": "Show Register Button",
|
||||||
"admin.config.require_sign_in_view": "Require Sign-In to View Pages",
|
"admin.config.require_sign_in_view": "Require Sign-In to View Pages",
|
||||||
"admin.config.mail_notify": "Enable Email Notifications",
|
"admin.config.mail_notify": "Enable Email Notifications",
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 172 B |
1
public/assets/img/svg/fontawesome-openid.svg
generated
1
public/assets/img/svg/fontawesome-openid.svg
generated
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 448" class="svg fontawesome-openid" width="16" height="16" aria-hidden="true"><path d="M271.5 0v384l-68 32C88.5 405.75 0 344.5 0 270.25c0-71.5 82.5-131 191.75-144.25v43c-71.5 12.5-124 53-124 101.25 0 51 58.5 93.25 135.75 103v-340zm167.25 145.5L448 243l-131.25-28.5 36.75-20.75c-19.5-11.5-43.5-20-70-24.75v-43c46.25 5.5 87.75 19.5 120.25 39.25z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 409 B |
@@ -56,9 +56,6 @@ type CommonAuthOptions struct {
|
|||||||
func prepareCommonAuthPageData(ctx *context.Context, opt CommonAuthOptions) {
|
func prepareCommonAuthPageData(ctx *context.Context, opt CommonAuthOptions) {
|
||||||
ctx.Data["EnablePasswordSignInForm"] = setting.Service.EnablePasswordSignInForm
|
ctx.Data["EnablePasswordSignInForm"] = setting.Service.EnablePasswordSignInForm
|
||||||
ctx.Data["EnablePasskeyAuth"] = setting.Service.EnablePasskeyAuth
|
ctx.Data["EnablePasskeyAuth"] = setting.Service.EnablePasskeyAuth
|
||||||
|
|
||||||
// for OpenID Connect
|
|
||||||
ctx.Data["EnableOpenIDSignUp"] = setting.Service.EnableOpenIDSignUp
|
|
||||||
ctx.Data["AllowOnlyInternalRegistration"] = setting.Service.AllowOnlyInternalRegistration
|
ctx.Data["AllowOnlyInternalRegistration"] = setting.Service.AllowOnlyInternalRegistration
|
||||||
|
|
||||||
if opt.EnableCaptcha {
|
if opt.EnableCaptcha {
|
||||||
@@ -230,7 +227,6 @@ func performAutoLoginOAuth2(ctx *context.Context, data *preparedSignInData) bool
|
|||||||
// If only 1 OAuth provider is present and other login methods are disabled, redirect to the OAuth provider.
|
// If only 1 OAuth provider is present and other login methods are disabled, redirect to the OAuth provider.
|
||||||
onlySingleOAuth2 := len(data.oauth2Providers) == 1 &&
|
onlySingleOAuth2 := len(data.oauth2Providers) == 1 &&
|
||||||
!setting.Service.EnablePasswordSignInForm &&
|
!setting.Service.EnablePasswordSignInForm &&
|
||||||
!setting.Service.EnableOpenIDSignIn &&
|
|
||||||
!setting.Service.EnablePasskeyAuth &&
|
!setting.Service.EnablePasskeyAuth &&
|
||||||
!data.enableSSPI
|
!data.enableSSPI
|
||||||
|
|
||||||
|
|||||||
@@ -17,11 +17,9 @@ import (
|
|||||||
"gitea.dev/modules/setting"
|
"gitea.dev/modules/setting"
|
||||||
"gitea.dev/modules/test"
|
"gitea.dev/modules/test"
|
||||||
"gitea.dev/modules/util"
|
"gitea.dev/modules/util"
|
||||||
"gitea.dev/modules/web"
|
|
||||||
auth_service "gitea.dev/services/auth"
|
auth_service "gitea.dev/services/auth"
|
||||||
"gitea.dev/services/auth/source/oauth2"
|
"gitea.dev/services/auth/source/oauth2"
|
||||||
"gitea.dev/services/contexttest"
|
"gitea.dev/services/contexttest"
|
||||||
"gitea.dev/services/forms"
|
|
||||||
|
|
||||||
"github.com/markbates/goth"
|
"github.com/markbates/goth"
|
||||||
"github.com/markbates/goth/gothic"
|
"github.com/markbates/goth/gothic"
|
||||||
@@ -103,10 +101,8 @@ func TestWebAuthOAuth2(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("RedirectSingleProvider", func(t *testing.T) {
|
t.Run("RedirectSingleProvider", func(t *testing.T) {
|
||||||
enablePassword := &setting.Service.EnablePasswordSignInForm
|
enablePassword := &setting.Service.EnablePasswordSignInForm
|
||||||
enableOpenID := &setting.Service.EnableOpenIDSignIn
|
|
||||||
enablePasskey := &setting.Service.EnablePasskeyAuth
|
enablePasskey := &setting.Service.EnablePasskeyAuth
|
||||||
defer test.MockVariableValue(enablePassword, false)()
|
defer test.MockVariableValue(enablePassword, false)()
|
||||||
defer test.MockVariableValue(enableOpenID, false)()
|
|
||||||
defer test.MockVariableValue(enablePasskey, false)()
|
defer test.MockVariableValue(enablePasskey, false)()
|
||||||
|
|
||||||
testSignIn := func(t *testing.T, link string, expectedCode int, expectedRedirect string) {
|
testSignIn := func(t *testing.T, link string, expectedCode int, expectedRedirect string) {
|
||||||
@@ -120,14 +116,12 @@ func TestWebAuthOAuth2(t *testing.T) {
|
|||||||
testSignIn(t, "/user/login", http.StatusSeeOther, "/user/oauth2/dummy+auth%27s%20source")
|
testSignIn(t, "/user/login", http.StatusSeeOther, "/user/oauth2/dummy+auth%27s%20source")
|
||||||
testSignIn(t, "/user/login?redirect_to=/", http.StatusSeeOther, "/user/oauth2/dummy+auth%27s%20source?redirect_to=%2F")
|
testSignIn(t, "/user/login?redirect_to=/", http.StatusSeeOther, "/user/oauth2/dummy+auth%27s%20source?redirect_to=%2F")
|
||||||
|
|
||||||
*enablePassword, *enableOpenID, *enablePasskey = true, false, false
|
*enablePassword, *enablePasskey = true, false
|
||||||
testSignIn(t, "/user/login", http.StatusOK, "")
|
testSignIn(t, "/user/login", http.StatusOK, "")
|
||||||
*enablePassword, *enableOpenID, *enablePasskey = false, true, false
|
*enablePassword, *enablePasskey = false, true
|
||||||
testSignIn(t, "/user/login", http.StatusOK, "")
|
|
||||||
*enablePassword, *enableOpenID, *enablePasskey = false, false, true
|
|
||||||
testSignIn(t, "/user/login", http.StatusOK, "")
|
testSignIn(t, "/user/login", http.StatusOK, "")
|
||||||
|
|
||||||
*enablePassword, *enableOpenID, *enablePasskey = false, false, false
|
*enablePassword, *enablePasskey = false, false
|
||||||
addOAuth2Source(t, "dummy-auth-source-2", oauth2.Source{})
|
addOAuth2Source(t, "dummy-auth-source-2", oauth2.Source{})
|
||||||
testSignIn(t, "/user/login", http.StatusOK, "")
|
testSignIn(t, "/user/login", http.StatusOK, "")
|
||||||
})
|
})
|
||||||
@@ -187,37 +181,6 @@ func TestWebAuthOAuth2(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOpenIDRequireTwoFactor(t *testing.T) {
|
|
||||||
require.NoError(t, unittest.PrepareTestDatabase())
|
|
||||||
mockOpt := contexttest.MockContextOption{SessionStore: session.NewMockMemStore("dummy-sid-openid")}
|
|
||||||
|
|
||||||
user32 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 32}) // has a webauthn credential
|
|
||||||
ctx, resp := contexttest.MockContext(t, "/user/openid/connect", mockOpt)
|
|
||||||
openIDRequireTwoFactor(ctx, user32, false, "https://example.com/id")
|
|
||||||
assert.Equal(t, "/user/webauthn", test.RedirectURL(resp))
|
|
||||||
unittest.AssertNotExistsBean(t, &user_model.UserOpenID{UID: user32.ID}) // not attached before the key answered
|
|
||||||
|
|
||||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
|
||||||
ctx, _ = contexttest.MockContext(t, "/user/openid/connect", mockOpt)
|
|
||||||
openIDRequireTwoFactor(ctx, user2, false, "https://example.com/id")
|
|
||||||
assert.False(t, ctx.Written())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRegisterOpenIDPostRejectsWrongCaptcha(t *testing.T) {
|
|
||||||
require.NoError(t, unittest.PrepareTestDatabase())
|
|
||||||
defer test.MockVariableValue(&setting.Service.EnableCaptcha, true)()
|
|
||||||
defer test.MockVariableValue(&setting.Service.CaptchaType, setting.ImageCaptcha)()
|
|
||||||
sess := session.NewMockMemStore("dummy-sid-openid-register")
|
|
||||||
require.NoError(t, sess.Set("openid_verified_uri", "https://example.com/openid"))
|
|
||||||
|
|
||||||
ctx, _ := contexttest.MockContext(t, "POST /user/openid/register", contexttest.MockContextOption{SessionStore: sess})
|
|
||||||
contexttest.MockRequestPostForm(ctx.Req, url.Values{"captcha_id": {"unknown"}, "captcha": {"000000"}})
|
|
||||||
web.SetForm(ctx, &forms.SignUpOpenIDForm{UserName: "openid-captcha-user", Email: "openid-captcha-user@example.com"})
|
|
||||||
RegisterOpenIDPost(ctx)
|
|
||||||
assert.Equal(t, true, ctx.Data["Err_Captcha"])
|
|
||||||
unittest.AssertNotExistsBean(t, &user_model.User{LowerName: "openid-captcha-user"})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAutoSignInStaleCookie(t *testing.T) {
|
func TestAutoSignInStaleCookie(t *testing.T) {
|
||||||
require.NoError(t, unittest.PrepareTestDatabase())
|
require.NoError(t, unittest.PrepareTestDatabase())
|
||||||
at, token, err := auth_service.CreateAuthTokenForUserID(t.Context(), 2)
|
at, token, err := auth_service.CreateAuthTokenForUserID(t.Context(), 2)
|
||||||
|
|||||||
@@ -263,15 +263,10 @@ func LinkAccountPostRegister(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func completePendingLinks(ctx *context.Context, user *user_model.User) error {
|
func completePendingLinks(ctx *context.Context, user *user_model.User) error {
|
||||||
if ctx.Session.Get("linkAccount") != nil {
|
if ctx.Session.Get("linkAccount") == nil {
|
||||||
if err := linkAccountFromContext(ctx, user); err != nil {
|
return nil
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return openIDConnectFromContext(ctx, user)
|
|
||||||
}
|
|
||||||
|
|
||||||
func linkAccountFromContext(ctx *context.Context, user *user_model.User) error {
|
|
||||||
linkAccountData := oauth2GetLinkAccountData(ctx)
|
linkAccountData := oauth2GetLinkAccountData(ctx)
|
||||||
if linkAccountData == nil {
|
if linkAccountData == nil {
|
||||||
return errors.New("not in LinkAccount session")
|
return errors.New("not in LinkAccount session")
|
||||||
|
|||||||
@@ -1,418 +0,0 @@
|
|||||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package auth
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
|
|
||||||
auth_model "gitea.dev/models/auth"
|
|
||||||
user_model "gitea.dev/models/user"
|
|
||||||
"gitea.dev/modules/auth/openid"
|
|
||||||
"gitea.dev/modules/log"
|
|
||||||
"gitea.dev/modules/setting"
|
|
||||||
"gitea.dev/modules/templates"
|
|
||||||
"gitea.dev/modules/util"
|
|
||||||
"gitea.dev/modules/web"
|
|
||||||
"gitea.dev/services/auth"
|
|
||||||
"gitea.dev/services/context"
|
|
||||||
"gitea.dev/services/forms"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
tplSignInOpenID templates.TplName = "user/auth/signin_openid"
|
|
||||||
tplConnectOID templates.TplName = "user/auth/signup_openid_connect"
|
|
||||||
tplSignUpOID templates.TplName = "user/auth/signup_openid_register"
|
|
||||||
)
|
|
||||||
|
|
||||||
// the OpenID is attached only after the second factor passed, so a stolen password cannot leave one behind
|
|
||||||
func openIDRequireTwoFactor(ctx *context.Context, u *user_model.User, remember bool, pendingURI string) {
|
|
||||||
hasTwoFactor, err := auth_model.HasTwoFactorOrWebAuthn(ctx, u.ID)
|
|
||||||
if err != nil {
|
|
||||||
ctx.ServerError("HasTwoFactorOrWebAuthn", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !hasTwoFactor {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
handleTwoFactorRequired(ctx, u, remember, map[string]any{"openidPendingURI": pendingURI})
|
|
||||||
}
|
|
||||||
|
|
||||||
func openIDConnectFromContext(ctx *context.Context, u *user_model.User) error {
|
|
||||||
uri, _ := ctx.Session.Get("openidPendingURI").(string)
|
|
||||||
if uri == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err := ctx.Session.Delete("openidPendingURI"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := user_model.AddUserOpenID(ctx, &user_model.UserOpenID{UID: u.ID, URI: uri}); err != nil {
|
|
||||||
if !user_model.IsErrOpenIDAlreadyUsed(err) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ctx.Flash.Error(ctx.Tr("form.openid_been_used", uri))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignInOpenID render sign in page
|
|
||||||
func SignInOpenID(ctx *context.Context) {
|
|
||||||
ctx.Data["Title"] = ctx.Tr("sign_in")
|
|
||||||
|
|
||||||
if ctx.FormString("openid.return_to") != "" {
|
|
||||||
signInOpenIDVerify(ctx)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if performAutoLogin(ctx) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Data["PageIsSignIn"] = true
|
|
||||||
ctx.Data["PageIsLoginOpenID"] = true
|
|
||||||
ctx.HTML(http.StatusOK, tplSignInOpenID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the given OpenID URI is allowed by blacklist/whitelist
|
|
||||||
func allowedOpenIDURI(uri string) (err error) {
|
|
||||||
// In case a Whitelist is present, URI must be in it
|
|
||||||
// in order to be accepted
|
|
||||||
if len(setting.Service.OpenIDWhitelist) != 0 {
|
|
||||||
for _, pat := range setting.Service.OpenIDWhitelist {
|
|
||||||
if pat.MatchString(uri) {
|
|
||||||
return nil // pass
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// must match one of this or be refused
|
|
||||||
return errors.New("URI not allowed by whitelist")
|
|
||||||
}
|
|
||||||
|
|
||||||
// A blacklist match expliclty forbids
|
|
||||||
for _, pat := range setting.Service.OpenIDBlacklist {
|
|
||||||
if pat.MatchString(uri) {
|
|
||||||
return errors.New("URI forbidden by blacklist")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignInOpenIDPost response for openid sign in request
|
|
||||||
func SignInOpenIDPost(ctx *context.Context) {
|
|
||||||
form := web.GetForm[*forms.SignInOpenIDForm](ctx)
|
|
||||||
ctx.Data["Title"] = ctx.Tr("sign_in")
|
|
||||||
ctx.Data["PageIsSignIn"] = true
|
|
||||||
ctx.Data["PageIsLoginOpenID"] = true
|
|
||||||
|
|
||||||
if ctx.HasError() {
|
|
||||||
ctx.HTML(http.StatusOK, tplSignInOpenID)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
id, err := openid.Normalize(form.Openid)
|
|
||||||
if err != nil {
|
|
||||||
ctx.RenderWithErrDeprecated(err.Error(), tplSignInOpenID, &form)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
form.Openid = id
|
|
||||||
|
|
||||||
log.Trace("OpenID uri: " + id)
|
|
||||||
|
|
||||||
err = allowedOpenIDURI(id)
|
|
||||||
if err != nil {
|
|
||||||
ctx.RenderWithErrDeprecated(err.Error(), tplSignInOpenID, &form)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
redirectTo := setting.AppURL + "user/login/openid"
|
|
||||||
url, err := openid.RedirectURL(id, redirectTo, setting.AppURL)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Error in OpenID redirect URL: %s, %v", redirectTo, err.Error())
|
|
||||||
ctx.RenderWithErrDeprecated("Unable to find OpenID provider in "+redirectTo, tplSignInOpenID, &form)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Request optional nickname and email info
|
|
||||||
// NOTE: change to `openid.sreg.required` to require it
|
|
||||||
url += "&openid.ns.sreg=http%3A%2F%2Fopenid.net%2Fextensions%2Fsreg%2F1.1"
|
|
||||||
url += "&openid.sreg.optional=nickname%2Cemail"
|
|
||||||
|
|
||||||
log.Trace("Form-passed openid-remember: %t", form.Remember)
|
|
||||||
|
|
||||||
if err := ctx.Session.Set("openid_signin_remember", form.Remember); err != nil {
|
|
||||||
log.Error("SignInOpenIDPost: Could not set openid_signin_remember in session: %v", err)
|
|
||||||
}
|
|
||||||
if err := ctx.Session.Release(); err != nil {
|
|
||||||
log.Error("SignInOpenIDPost: Unable to save changes to the session: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Redirect(url)
|
|
||||||
}
|
|
||||||
|
|
||||||
// signInOpenIDVerify handles response from OpenID provider
|
|
||||||
func signInOpenIDVerify(ctx *context.Context) {
|
|
||||||
log.Trace("Incoming call to: %s", ctx.Req.URL.String())
|
|
||||||
|
|
||||||
fullURL := setting.AppURL + ctx.Req.URL.String()[1:]
|
|
||||||
log.Trace("Full URL: %s", fullURL)
|
|
||||||
|
|
||||||
id, err := openid.Verify(fullURL)
|
|
||||||
if err != nil {
|
|
||||||
ctx.RenderWithErrDeprecated(err.Error(), tplSignInOpenID, &forms.SignInOpenIDForm{
|
|
||||||
Openid: id,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Trace("Verified ID: %s", id)
|
|
||||||
|
|
||||||
/* Now we should seek for the user and log him in, or prompt
|
|
||||||
* to register if not found */
|
|
||||||
|
|
||||||
u, err := user_model.GetUserByOpenID(ctx, id)
|
|
||||||
if err != nil {
|
|
||||||
if !user_model.IsErrUserNotExist(err) {
|
|
||||||
ctx.RenderWithErrDeprecated(err.Error(), tplSignInOpenID, &forms.SignInOpenIDForm{
|
|
||||||
Openid: id,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Error("signInOpenIDVerify: %v", err)
|
|
||||||
}
|
|
||||||
if u != nil {
|
|
||||||
log.Trace("User exists, logging in")
|
|
||||||
remember, _ := ctx.Session.Get("openid_signin_remember").(bool)
|
|
||||||
log.Trace("Session stored openid-remember: %t", remember)
|
|
||||||
openIDRequireTwoFactor(ctx, u, remember, "")
|
|
||||||
if ctx.Written() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
handleSignIn(ctx, u, remember)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Trace("User with openid: %s does not exist, should connect or register", id)
|
|
||||||
|
|
||||||
parsedURL, err := url.Parse(fullURL)
|
|
||||||
if err != nil {
|
|
||||||
ctx.RenderWithErrDeprecated(err.Error(), tplSignInOpenID, &forms.SignInOpenIDForm{
|
|
||||||
Openid: id,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
values, err := url.ParseQuery(parsedURL.RawQuery)
|
|
||||||
if err != nil {
|
|
||||||
ctx.RenderWithErrDeprecated(err.Error(), tplSignInOpenID, &forms.SignInOpenIDForm{
|
|
||||||
Openid: id,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
email := values.Get("openid.sreg.email")
|
|
||||||
nickname := values.Get("openid.sreg.nickname")
|
|
||||||
|
|
||||||
log.Trace("User has email=%s and nickname=%s", email, nickname)
|
|
||||||
|
|
||||||
if email != "" {
|
|
||||||
u, err = user_model.GetUserByEmail(ctx, email)
|
|
||||||
if err != nil {
|
|
||||||
if !user_model.IsErrUserNotExist(err) {
|
|
||||||
ctx.RenderWithErrDeprecated(err.Error(), tplSignInOpenID, &forms.SignInOpenIDForm{
|
|
||||||
Openid: id,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Error("signInOpenIDVerify: %v", err)
|
|
||||||
}
|
|
||||||
if u != nil {
|
|
||||||
log.Trace("Local user %s has OpenID provided email %s", u.LowerName, email)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if u == nil && nickname != "" {
|
|
||||||
u, _ = user_model.GetUserByName(ctx, nickname)
|
|
||||||
if err != nil {
|
|
||||||
if !user_model.IsErrUserNotExist(err) {
|
|
||||||
ctx.RenderWithErrDeprecated(err.Error(), tplSignInOpenID, &forms.SignInOpenIDForm{
|
|
||||||
Openid: id,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if u != nil {
|
|
||||||
log.Trace("Local user %s has OpenID provided nickname %s", u.LowerName, nickname)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if u != nil {
|
|
||||||
nickname = u.LowerName
|
|
||||||
}
|
|
||||||
if err := regenerateSession(ctx, map[string]any{
|
|
||||||
"openid_verified_uri": id,
|
|
||||||
"openid_determined_email": email,
|
|
||||||
"openid_determined_username": nickname,
|
|
||||||
}); err != nil {
|
|
||||||
ctx.ServerError("updateSession", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if u != nil || !setting.Service.EnableOpenIDSignUp || setting.Service.AllowOnlyInternalRegistration {
|
|
||||||
ctx.Redirect(setting.AppSubURL + "/user/openid/connect")
|
|
||||||
} else {
|
|
||||||
ctx.Redirect(setting.AppSubURL + "/user/openid/register")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func prepareConnectOpenIDPageData(ctx *context.Context) (oid string) {
|
|
||||||
oid, _ = ctx.Session.Get("openid_verified_uri").(string)
|
|
||||||
if oid == "" {
|
|
||||||
ctx.Redirect(setting.AppSubURL + "/user/login/openid")
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
ctx.Data["Title"] = "OpenID connect"
|
|
||||||
ctx.Data["PageIsSignIn"] = true
|
|
||||||
ctx.Data["PageIsOpenIDConnect"] = true
|
|
||||||
ctx.Data["OpenID"] = oid
|
|
||||||
prepareCommonAuthPageData(ctx, CommonAuthOptions{EnableCaptcha: false})
|
|
||||||
return oid
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConnectOpenID shows a form to connect an OpenID URI to an existing account
|
|
||||||
func ConnectOpenID(ctx *context.Context) {
|
|
||||||
oid := prepareConnectOpenIDPageData(ctx)
|
|
||||||
if oid == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
userName, _ := ctx.Session.Get("openid_determined_username").(string)
|
|
||||||
if userName != "" {
|
|
||||||
ctx.Data["user_name"] = userName
|
|
||||||
}
|
|
||||||
ctx.HTML(http.StatusOK, tplConnectOID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConnectOpenIDPost handles submission of a form to connect an OpenID URI to an existing account
|
|
||||||
func ConnectOpenIDPost(ctx *context.Context) {
|
|
||||||
form := web.GetForm[*forms.ConnectOpenIDForm](ctx)
|
|
||||||
oid := prepareConnectOpenIDPageData(ctx)
|
|
||||||
if oid == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
u, _, err := auth.UserSignIn(ctx, form.UserName, form.Password)
|
|
||||||
if err != nil {
|
|
||||||
handleSignInError(ctx, form.UserName, &form, tplConnectOID, "ConnectOpenIDPost", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
remember, _ := ctx.Session.Get("openid_signin_remember").(bool)
|
|
||||||
openIDRequireTwoFactor(ctx, u, remember, oid)
|
|
||||||
if ctx.Written() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
userOID := &user_model.UserOpenID{UID: u.ID, URI: oid}
|
|
||||||
if err := user_model.AddUserOpenID(ctx, userOID); err != nil {
|
|
||||||
if user_model.IsErrOpenIDAlreadyUsed(err) {
|
|
||||||
ctx.RenderWithErrDeprecated(ctx.Tr("form.openid_been_used", oid), tplConnectOID, &form)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ctx.ServerError("AddUserOpenID", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Flash.Success(ctx.Tr("settings.add_openid_success"))
|
|
||||||
handleSignIn(ctx, u, remember)
|
|
||||||
}
|
|
||||||
|
|
||||||
func prepareRegisterOpenIDPageData(ctx *context.Context) (oid string) {
|
|
||||||
oid, _ = ctx.Session.Get("openid_verified_uri").(string)
|
|
||||||
if oid == "" {
|
|
||||||
ctx.Redirect(setting.AppSubURL + "/user/login/openid")
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
ctx.Data["Title"] = "OpenID signup"
|
|
||||||
ctx.Data["PageIsSignIn"] = true
|
|
||||||
ctx.Data["PageIsOpenIDRegister"] = true
|
|
||||||
ctx.Data["OpenID"] = oid
|
|
||||||
prepareCommonAuthPageData(ctx, CommonAuthOptions{
|
|
||||||
EnableCaptcha: setting.Service.EnableCaptcha,
|
|
||||||
})
|
|
||||||
return oid
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterOpenID shows a form to create a new user authenticated via an OpenID URI
|
|
||||||
func RegisterOpenID(ctx *context.Context) {
|
|
||||||
oid := prepareRegisterOpenIDPageData(ctx)
|
|
||||||
if oid == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
userName, _ := ctx.Session.Get("openid_determined_username").(string)
|
|
||||||
if userName != "" {
|
|
||||||
ctx.Data["user_name"] = userName
|
|
||||||
}
|
|
||||||
email, _ := ctx.Session.Get("openid_determined_email").(string)
|
|
||||||
if email != "" {
|
|
||||||
ctx.Data["email"] = email
|
|
||||||
}
|
|
||||||
ctx.HTML(http.StatusOK, tplSignUpOID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterOpenIDPost handles submission of a form to create a new user authenticated via an OpenID URI
|
|
||||||
func RegisterOpenIDPost(ctx *context.Context) {
|
|
||||||
oid := prepareRegisterOpenIDPageData(ctx)
|
|
||||||
if oid == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
form := web.GetForm[*forms.SignUpOpenIDForm](ctx)
|
|
||||||
|
|
||||||
if setting.Service.AllowOnlyInternalRegistration {
|
|
||||||
ctx.HTTPError(http.StatusForbidden)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if setting.Service.EnableCaptcha {
|
|
||||||
if err := ctx.Req.ParseForm(); err != nil {
|
|
||||||
ctx.ServerError("", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !context.VerifyCaptcha(ctx, tplSignUpOID, form) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
length := max(setting.MinPasswordLength, 256)
|
|
||||||
password := util.CryptoRandomString(int64(length))
|
|
||||||
|
|
||||||
u := &user_model.User{
|
|
||||||
Name: form.UserName,
|
|
||||||
Email: form.Email,
|
|
||||||
Passwd: password,
|
|
||||||
}
|
|
||||||
if !createUserInContext(ctx, tplSignUpOID, form, u, nil, nil) {
|
|
||||||
// error already handled
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// add OpenID for the user
|
|
||||||
userOID := &user_model.UserOpenID{UID: u.ID, URI: oid}
|
|
||||||
if err := user_model.AddUserOpenID(ctx, userOID); err != nil {
|
|
||||||
if user_model.IsErrOpenIDAlreadyUsed(err) {
|
|
||||||
ctx.RenderWithErrDeprecated(ctx.Tr("form.openid_been_used", oid), tplSignUpOID, &form)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ctx.ServerError("AddUserOpenID", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !handleUserCreated(ctx, u, nil) {
|
|
||||||
// error already handled
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
remember, _ := ctx.Session.Get("openid_signin_remember").(bool)
|
|
||||||
log.Trace("Session stored openid-remember: %t", remember)
|
|
||||||
handleSignIn(ctx, u, remember)
|
|
||||||
}
|
|
||||||
@@ -31,13 +31,6 @@ func prepareContextForProfileBigAvatar(ctx *context.Context) {
|
|||||||
if setting.Service.UserLocationMapURL != "" {
|
if setting.Service.UserLocationMapURL != "" {
|
||||||
ctx.Data["ContextUserLocationMapURL"] = setting.Service.UserLocationMapURL + url.QueryEscape(ctx.ContextUser.Location)
|
ctx.Data["ContextUserLocationMapURL"] = setting.Service.UserLocationMapURL + url.QueryEscape(ctx.ContextUser.Location)
|
||||||
}
|
}
|
||||||
// Show OpenID URIs
|
|
||||||
openIDs, err := user_model.GetUserOpenIDs(ctx, ctx.ContextUser.ID)
|
|
||||||
if err != nil {
|
|
||||||
ctx.ServerError("GetUserOpenIDs", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ctx.Data["OpenIDs"] = openIDs
|
|
||||||
if len(ctx.ContextUser.Description) != 0 {
|
if len(ctx.ContextUser.Description) != 0 {
|
||||||
content, err := markdown.RenderString(markup.NewRenderContext(ctx).WithMetas(markup.ComposeSimpleDocumentMetas()), ctx.ContextUser.Description)
|
content, err := markdown.RenderString(markup.NewRenderContext(ctx).WithMetas(markup.ComposeSimpleDocumentMetas()), ctx.ContextUser.Description)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,180 +0,0 @@
|
|||||||
// Copyright 2018 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package security
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
audit_model "gitea.dev/models/audit"
|
|
||||||
user_model "gitea.dev/models/user"
|
|
||||||
"gitea.dev/modules/auth/openid"
|
|
||||||
"gitea.dev/modules/base"
|
|
||||||
"gitea.dev/modules/log"
|
|
||||||
"gitea.dev/modules/setting"
|
|
||||||
"gitea.dev/modules/util"
|
|
||||||
"gitea.dev/modules/web"
|
|
||||||
"gitea.dev/services/audit"
|
|
||||||
"gitea.dev/services/context"
|
|
||||||
"gitea.dev/services/forms"
|
|
||||||
)
|
|
||||||
|
|
||||||
const openIDAssociationCodeMinutes = 10
|
|
||||||
|
|
||||||
// OpenIDPost response for change user's openid
|
|
||||||
func OpenIDPost(ctx *context.Context) {
|
|
||||||
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageCredentials) {
|
|
||||||
ctx.HTTPError(http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
form := web.GetForm[*forms.AddOpenIDForm](ctx)
|
|
||||||
ctx.Data["Title"] = ctx.Tr("settings_title")
|
|
||||||
ctx.Data["PageIsSettingsSecurity"] = true
|
|
||||||
|
|
||||||
if ctx.HasError() {
|
|
||||||
loadSecurityData(ctx)
|
|
||||||
|
|
||||||
ctx.HTML(http.StatusOK, tplSettingsSecurity)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// WARNING: specifying a wrong OpenID here could lock
|
|
||||||
// a user out of her account, would be better to
|
|
||||||
// verify/confirm the new OpenID before storing it
|
|
||||||
|
|
||||||
// Also, consider allowing for multiple OpenID URIs
|
|
||||||
|
|
||||||
id, err := openid.Normalize(form.Openid)
|
|
||||||
if err != nil {
|
|
||||||
loadSecurityData(ctx)
|
|
||||||
|
|
||||||
ctx.RenderWithErrDeprecated(err.Error(), tplSettingsSecurity, &form)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
form.Openid = id
|
|
||||||
log.Trace("Normalized id: " + id)
|
|
||||||
|
|
||||||
oids, err := user_model.GetUserOpenIDs(ctx, ctx.Doer.ID)
|
|
||||||
if err != nil {
|
|
||||||
ctx.ServerError("GetUserOpenIDs", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ctx.Data["OpenIDs"] = oids
|
|
||||||
|
|
||||||
// Check that the OpenID is not already used
|
|
||||||
for _, obj := range oids {
|
|
||||||
if obj.URI == id {
|
|
||||||
loadSecurityData(ctx)
|
|
||||||
|
|
||||||
ctx.RenderWithErrDeprecated(ctx.Tr("form.openid_been_used", id), tplSettingsSecurity, &form)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
state := base.CreateTimeLimitCode(fmt.Sprintf("openid_association:%d", ctx.Doer.ID), openIDAssociationCodeMinutes, time.Now(), nil)
|
|
||||||
redirectTo := setting.AppURL + "user/settings/security?state=" + state
|
|
||||||
url, err := openid.RedirectURL(id, redirectTo, setting.AppURL)
|
|
||||||
if err != nil {
|
|
||||||
loadSecurityData(ctx)
|
|
||||||
|
|
||||||
ctx.RenderWithErrDeprecated(err.Error(), tplSettingsSecurity, &form)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ctx.Redirect(url)
|
|
||||||
}
|
|
||||||
|
|
||||||
func settingsOpenIDVerify(ctx *context.Context) {
|
|
||||||
if !base.VerifyTimeLimitCode(time.Now(), fmt.Sprintf("openid_association:%d", ctx.Doer.ID), openIDAssociationCodeMinutes, ctx.FormString("state")) {
|
|
||||||
ctx.HTTPError(http.StatusForbidden)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Trace("Incoming call to: " + ctx.Req.URL.String())
|
|
||||||
|
|
||||||
fullURL := setting.AppURL + ctx.Req.URL.String()[1:]
|
|
||||||
log.Trace("Full URL: " + fullURL)
|
|
||||||
|
|
||||||
id, err := openid.Verify(fullURL)
|
|
||||||
if err != nil {
|
|
||||||
ctx.RenderWithErrDeprecated(err.Error(), tplSettingsSecurity, &forms.AddOpenIDForm{
|
|
||||||
Openid: id,
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Trace("Verified ID: " + id)
|
|
||||||
|
|
||||||
oid := &user_model.UserOpenID{UID: ctx.Doer.ID, URI: id}
|
|
||||||
if err = user_model.AddUserOpenID(ctx, oid); err != nil {
|
|
||||||
if user_model.IsErrOpenIDAlreadyUsed(err) {
|
|
||||||
ctx.RenderWithErrDeprecated(ctx.Tr("form.openid_been_used", id), tplSettingsSecurity, &forms.AddOpenIDForm{Openid: id})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ctx.ServerError("AddUserOpenID", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Trace("Associated OpenID %s to user %s", id, ctx.Doer.Name)
|
|
||||||
|
|
||||||
audit.Record(ctx, audit_model.UserOpenIDAdd, ctx.Doer, "openid", oid.URI)
|
|
||||||
|
|
||||||
ctx.Flash.Success(ctx.Tr("settings.add_openid_success"))
|
|
||||||
|
|
||||||
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteOpenID response for delete user's openid
|
|
||||||
func DeleteOpenID(ctx *context.Context) {
|
|
||||||
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageCredentials) {
|
|
||||||
ctx.HTTPError(http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
oid, err := user_model.GetUserOpenIDByID(ctx, ctx.FormInt64("id"), ctx.Doer.ID)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, util.ErrNotExist) {
|
|
||||||
ctx.HTTPError(http.StatusNotFound)
|
|
||||||
} else {
|
|
||||||
ctx.ServerError("GetUserOpenIDByID", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := user_model.DeleteUserOpenID(ctx, oid); err != nil {
|
|
||||||
if errors.Is(err, util.ErrNotExist) {
|
|
||||||
ctx.HTTPError(http.StatusNotFound)
|
|
||||||
} else {
|
|
||||||
ctx.ServerError("DeleteUserOpenID", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
audit.Record(ctx, audit_model.UserOpenIDRemove, ctx.Doer, "openid", oid.URI)
|
|
||||||
|
|
||||||
log.Trace("OpenID address deleted: %s", ctx.Doer.Name)
|
|
||||||
|
|
||||||
ctx.Flash.Success(ctx.Tr("settings.openid_deletion_success"))
|
|
||||||
ctx.JSONRedirect(setting.AppSubURL + "/user/settings/security")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToggleOpenIDVisibility response for toggle visibility of user's openid
|
|
||||||
func ToggleOpenIDVisibility(ctx *context.Context) {
|
|
||||||
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageCredentials) {
|
|
||||||
ctx.HTTPError(http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := user_model.ToggleUserOpenIDVisibility(ctx, ctx.FormInt64("id"), ctx.Doer); err != nil {
|
|
||||||
if errors.Is(err, util.ErrNotExist) {
|
|
||||||
ctx.HTTPError(http.StatusNotFound)
|
|
||||||
} else {
|
|
||||||
ctx.ServerError("ToggleUserOpenIDVisibility", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
|
|
||||||
}
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package security
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"net/url"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"gitea.dev/models/unittest"
|
|
||||||
user_model "gitea.dev/models/user"
|
|
||||||
"gitea.dev/modules/base"
|
|
||||||
"gitea.dev/modules/setting"
|
|
||||||
"gitea.dev/modules/test"
|
|
||||||
"gitea.dev/modules/web"
|
|
||||||
"gitea.dev/services/context"
|
|
||||||
"gitea.dev/services/contexttest"
|
|
||||||
"gitea.dev/services/forms"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestOpenIDAssociation(t *testing.T) {
|
|
||||||
unittest.PrepareTestEnv(t)
|
|
||||||
defer test.MockVariableValue(&setting.AppURL, "https://gitea.example/")()
|
|
||||||
defer test.MockVariableValue(&setting.AppSubURL, "")()
|
|
||||||
defer test.MockVariableValue(&setting.Security.AllowedHostList, "127.0.0.1:*")()
|
|
||||||
|
|
||||||
provider := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.Method == http.MethodPost {
|
|
||||||
assert.Equal(t, "check_authentication", r.FormValue("openid.mode"))
|
|
||||||
w.Header().Set("Content-Type", "text/plain")
|
|
||||||
fmt.Fprint(w, "ns:http://specs.openid.net/auth/2.0\nis_valid:true\n")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.Header().Set("Content-Type", "text/html")
|
|
||||||
fmt.Fprintf(w, `<html><head><link rel="openid2.provider" href="http://%s/provider"></head></html>`, r.Host)
|
|
||||||
}))
|
|
||||||
defer provider.Close()
|
|
||||||
identity := provider.URL + "/identity"
|
|
||||||
ctx, resp := contexttest.MockContext(t, "POST /user/settings/security/openid")
|
|
||||||
contexttest.LoadUser(t, ctx, 2)
|
|
||||||
web.SetForm(ctx, &forms.AddOpenIDForm{Openid: identity})
|
|
||||||
OpenIDPost(ctx)
|
|
||||||
require.Equal(t, http.StatusSeeOther, resp.Code)
|
|
||||||
redirect, err := url.Parse(resp.Header().Get("Location"))
|
|
||||||
require.NoError(t, err)
|
|
||||||
callback, err := url.Parse(redirect.Query().Get("openid.return_to"))
|
|
||||||
require.NoError(t, err)
|
|
||||||
state := callback.Query().Get("state")
|
|
||||||
require.NotEmpty(t, state)
|
|
||||||
|
|
||||||
params := url.Values{
|
|
||||||
"openid.ns": {"http://specs.openid.net/auth/2.0"},
|
|
||||||
"openid.mode": {"id_res"},
|
|
||||||
"openid.op_endpoint": {provider.URL + "/provider"},
|
|
||||||
"openid.claimed_id": {identity},
|
|
||||||
"openid.identity": {identity},
|
|
||||||
"openid.return_to": {callback.String()},
|
|
||||||
"openid.response_nonce": {time.Now().UTC().Format("2006-01-02T15:04:05Z") + "association"},
|
|
||||||
"openid.assoc_handle": {"test-association"},
|
|
||||||
"openid.signed": {"op_endpoint,claimed_id,identity,return_to,response_nonce,assoc_handle"},
|
|
||||||
"openid.sig": {"test-signature"},
|
|
||||||
}
|
|
||||||
for _, tc := range []struct {
|
|
||||||
name string
|
|
||||||
uid int64
|
|
||||||
state string
|
|
||||||
}{
|
|
||||||
{"unsolicited", 2, ""},
|
|
||||||
{"wrong state", 2, "wrong"},
|
|
||||||
{"other user", 1, state},
|
|
||||||
{"expired", 2, base.CreateTimeLimitCode("openid_association:2", openIDAssociationCodeMinutes, time.Now().Add(-time.Hour), nil)},
|
|
||||||
{"other purpose", 2, base.CreateTimeLimitCode("reset_password:2", openIDAssociationCodeMinutes, time.Now(), nil)},
|
|
||||||
} {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
params.Set("state", tc.state)
|
|
||||||
ctx, resp := contexttest.MockContext(t, "GET /user/settings/security?"+params.Encode())
|
|
||||||
contexttest.LoadUser(t, ctx, tc.uid)
|
|
||||||
Security(ctx)
|
|
||||||
assert.Equal(t, http.StatusForbidden, resp.Code)
|
|
||||||
unittest.AssertNotExistsBean(t, &user_model.UserOpenID{URI: identity})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
params.Set("state", state)
|
|
||||||
ctx, resp = contexttest.MockContext(t, "GET /user/settings/security?"+params.Encode())
|
|
||||||
contexttest.LoadUser(t, ctx, 2)
|
|
||||||
Security(ctx)
|
|
||||||
require.Equal(t, http.StatusSeeOther, resp.Code)
|
|
||||||
unittest.AssertExistsAndLoadBean(t, &user_model.UserOpenID{UID: 2, URI: identity})
|
|
||||||
|
|
||||||
ctx, resp = contexttest.MockContext(t, "GET /user/settings/security?"+params.Encode())
|
|
||||||
contexttest.LoadUser(t, ctx, 2)
|
|
||||||
Security(ctx)
|
|
||||||
assert.Equal(t, http.StatusOK, resp.Code)
|
|
||||||
assert.Equal(t, "Nonce already used", ctx.Flash.ErrorMsg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOpenIDReturnsNotFoundForOtherUsersAddress(t *testing.T) {
|
|
||||||
unittest.PrepareTestEnv(t)
|
|
||||||
for _, handler := range []func(*context.Context){DeleteOpenID, ToggleOpenIDVisibility} {
|
|
||||||
ctx, _ := contexttest.MockContext(t, "POST /user/settings/security?id=1")
|
|
||||||
contexttest.LoadUser(t, ctx, 2)
|
|
||||||
handler(ctx)
|
|
||||||
assert.Equal(t, http.StatusNotFound, ctx.Resp.WrittenStatus())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -36,11 +36,6 @@ func Security(ctx *context.Context) {
|
|||||||
ctx.Data["Title"] = ctx.Tr("settings.security")
|
ctx.Data["Title"] = ctx.Tr("settings.security")
|
||||||
ctx.Data["PageIsSettingsSecurity"] = true
|
ctx.Data["PageIsSettingsSecurity"] = true
|
||||||
|
|
||||||
if ctx.FormString("openid.return_to") != "" {
|
|
||||||
settingsOpenIDVerify(ctx)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
loadSecurityData(ctx)
|
loadSecurityData(ctx)
|
||||||
|
|
||||||
ctx.HTML(http.StatusOK, tplSettingsSecurity)
|
ctx.HTML(http.StatusOK, tplSettingsSecurity)
|
||||||
@@ -153,11 +148,4 @@ func loadSecurityData(ctx *context.Context) {
|
|||||||
|
|
||||||
ctx.Data["OrderedOAuth2Names"] = orderedOAuth2Names
|
ctx.Data["OrderedOAuth2Names"] = orderedOAuth2Names
|
||||||
ctx.Data["OAuth2Providers"] = oauth2Providers
|
ctx.Data["OAuth2Providers"] = oauth2Providers
|
||||||
|
|
||||||
openid, err := user_model.GetUserOpenIDs(ctx, ctx.Doer.ID)
|
|
||||||
if err != nil {
|
|
||||||
ctx.ServerError("GetUserOpenIDs", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ctx.Data["OpenIDs"] = openid
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -358,20 +358,6 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
|
|||||||
// middleware: only apply CrossOriginProtection
|
// middleware: only apply CrossOriginProtection
|
||||||
crossOriginProtect := verifyAuthWithOptionsWeb(&common.VerifyOptions{DisableCrossOriginProtection: false})
|
crossOriginProtect := verifyAuthWithOptionsWeb(&common.VerifyOptions{DisableCrossOriginProtection: false})
|
||||||
|
|
||||||
openIDSignInEnabled := func(ctx *context.Context) {
|
|
||||||
if !setting.Service.EnableOpenIDSignIn {
|
|
||||||
ctx.HTTPError(http.StatusForbidden)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
openIDSignUpEnabled := func(ctx *context.Context) {
|
|
||||||
if !setting.Service.EnableOpenIDSignUp {
|
|
||||||
ctx.HTTPError(http.StatusForbidden)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
oauth2Enabled := func(ctx *context.Context) {
|
oauth2Enabled := func(ctx *context.Context) {
|
||||||
if !setting.OAuth2.Enabled {
|
if !setting.OAuth2.Enabled {
|
||||||
ctx.HTTPError(http.StatusForbidden)
|
ctx.HTTPError(http.StatusForbidden)
|
||||||
@@ -579,21 +565,6 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
|
|||||||
m.Get("/user/login", auth.SignIn)
|
m.Get("/user/login", auth.SignIn)
|
||||||
m.Group("/user", func() {
|
m.Group("/user", func() {
|
||||||
m.Post("/login", web.Bind[*forms.SignInForm](), auth.SignInPost)
|
m.Post("/login", web.Bind[*forms.SignInForm](), auth.SignInPost)
|
||||||
m.Group("", func() {
|
|
||||||
m.Combo("/login/openid").
|
|
||||||
Get(auth.SignInOpenID).
|
|
||||||
Post(web.Bind[*forms.SignInOpenIDForm](), auth.SignInOpenIDPost)
|
|
||||||
}, openIDSignInEnabled)
|
|
||||||
m.Group("/openid", func() {
|
|
||||||
m.Combo("/connect").
|
|
||||||
Get(auth.ConnectOpenID).
|
|
||||||
Post(web.Bind[*forms.ConnectOpenIDForm](), auth.ConnectOpenIDPost)
|
|
||||||
m.Group("/register", func() {
|
|
||||||
m.Combo("").
|
|
||||||
Get(auth.RegisterOpenID, openIDSignUpEnabled).
|
|
||||||
Post(web.Bind[*forms.SignUpOpenIDForm](), auth.RegisterOpenIDPost)
|
|
||||||
}, openIDSignUpEnabled)
|
|
||||||
}, openIDSignInEnabled)
|
|
||||||
m.Get("/sign_up", auth.SignUp)
|
m.Get("/sign_up", auth.SignUp)
|
||||||
m.Post("/sign_up", web.Bind[*forms.RegisterForm](), auth.SignUpPost)
|
m.Post("/sign_up", web.Bind[*forms.RegisterForm](), auth.SignUpPost)
|
||||||
m.Get("/link_account", auth.LinkAccount)
|
m.Get("/link_account", auth.LinkAccount)
|
||||||
@@ -671,11 +642,6 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
|
|||||||
m.Post("/rename", security.WebauthnRename)
|
m.Post("/rename", security.WebauthnRename)
|
||||||
m.Post("/delete", security.WebauthnDelete)
|
m.Post("/delete", security.WebauthnDelete)
|
||||||
})
|
})
|
||||||
m.Group("/openid", func() {
|
|
||||||
m.Post("", web.Bind[*forms.AddOpenIDForm](), security.OpenIDPost)
|
|
||||||
m.Post("/delete", security.DeleteOpenID)
|
|
||||||
m.Post("/toggle_visibility", security.ToggleOpenIDVisibility)
|
|
||||||
}, openIDSignInEnabled)
|
|
||||||
m.Post("/account_link", security.DeleteAccountLink)
|
m.Post("/account_link", security.DeleteAccountLink)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -61,14 +61,9 @@ func (s *Session) Verify(req *http.Request, w http.ResponseWriter, store DataSto
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ClearSessionKeysForSignIn(sess SessionStore) {
|
func ClearSessionKeysForSignIn(sess SessionStore) {
|
||||||
_ = sess.Delete("openid_verified_uri")
|
|
||||||
_ = sess.Delete("openid_signin_remember")
|
|
||||||
_ = sess.Delete("openid_determined_email")
|
|
||||||
_ = sess.Delete("openid_determined_username")
|
|
||||||
_ = sess.Delete("twofaUid")
|
_ = sess.Delete("twofaUid")
|
||||||
_ = sess.Delete("twofaRemember")
|
_ = sess.Delete("twofaRemember")
|
||||||
_ = sess.Delete("webauthnAssertion")
|
_ = sess.Delete("webauthnAssertion")
|
||||||
_ = sess.Delete("linkAccount")
|
_ = sess.Delete("linkAccount")
|
||||||
_ = sess.Delete("linkAccountData")
|
_ = sess.Delete("linkAccountData")
|
||||||
_ = sess.Delete("openidPendingURI")
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import (
|
|||||||
user_model "gitea.dev/models/user"
|
user_model "gitea.dev/models/user"
|
||||||
"gitea.dev/modules/log"
|
"gitea.dev/modules/log"
|
||||||
"gitea.dev/modules/optional"
|
"gitea.dev/modules/optional"
|
||||||
"gitea.dev/modules/setting"
|
|
||||||
"gitea.dev/modules/templates"
|
"gitea.dev/modules/templates"
|
||||||
"gitea.dev/services/audit"
|
"gitea.dev/services/audit"
|
||||||
"gitea.dev/services/auth/source/sspi"
|
"gitea.dev/services/auth/source/sspi"
|
||||||
@@ -87,7 +86,6 @@ func (s *SSPI) Verify(req *http.Request, w http.ResponseWriter, store DataStore,
|
|||||||
store.GetData()["Flash"] = map[string]string{
|
store.GetData()["Flash"] = map[string]string{
|
||||||
"ErrorMsg": err.Error(),
|
"ErrorMsg": err.Error(),
|
||||||
}
|
}
|
||||||
store.GetData()["EnableOpenIDSignIn"] = setting.Service.EnableOpenIDSignIn
|
|
||||||
store.GetData()["EnableSSPI"] = true
|
store.GetData()["EnableSSPI"] = true
|
||||||
// in this case, the Verify function is called in Gitea's web context
|
// in this case, the Verify function is called in Gitea's web context
|
||||||
// FIXME: it doesn't look good to render the page here, why not redirect?
|
// FIXME: it doesn't look good to render the page here, why not redirect?
|
||||||
|
|||||||
@@ -206,12 +206,6 @@ type ChangePasswordForm struct {
|
|||||||
Retype string `form:"retype"`
|
Retype string `form:"retype"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddOpenIDForm is for changing openid uri
|
|
||||||
type AddOpenIDForm struct {
|
|
||||||
middleware.FormDefaultValidator
|
|
||||||
Openid string `binding:"Required;MaxSize(256)"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddKeyForm form for adding SSH/GPG key
|
// AddKeyForm form for adding SSH/GPG key
|
||||||
type AddKeyForm struct {
|
type AddKeyForm struct {
|
||||||
middleware.FormDefaultValidator
|
middleware.FormDefaultValidator
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package forms
|
|
||||||
|
|
||||||
import "gitea.dev/modules/web/middleware"
|
|
||||||
|
|
||||||
// SignInOpenIDForm form for signing in with OpenID
|
|
||||||
type SignInOpenIDForm struct {
|
|
||||||
middleware.FormDefaultValidator
|
|
||||||
Openid string `binding:"Required;MaxSize(256)"`
|
|
||||||
Remember bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignUpOpenIDForm form for signin up with OpenID
|
|
||||||
type SignUpOpenIDForm struct {
|
|
||||||
middleware.FormDefaultValidator
|
|
||||||
UserName string `binding:"Required;Username;MaxSize(40)"`
|
|
||||||
Email string `binding:"Required;Email;MaxSize(254)"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConnectOpenIDForm form for connecting an existing account to an OpenID URI
|
|
||||||
type ConnectOpenIDForm struct {
|
|
||||||
middleware.FormDefaultValidator
|
|
||||||
UserName string `binding:"Required;MaxSize(254)"`
|
|
||||||
Password string `binding:"Required;MaxSize(255)"`
|
|
||||||
}
|
|
||||||
@@ -82,7 +82,6 @@ func deleteUser(ctx context.Context, u *user_model.User, purge bool) (err error)
|
|||||||
&activities_model.Action{UserID: u.ID},
|
&activities_model.Action{UserID: u.ID},
|
||||||
&issues_model.IssueUser{UID: u.ID},
|
&issues_model.IssueUser{UID: u.ID},
|
||||||
&user_model.EmailAddress{UID: u.ID},
|
&user_model.EmailAddress{UID: u.ID},
|
||||||
&user_model.UserOpenID{UID: u.ID},
|
|
||||||
&issues_model.Reaction{UserID: u.ID},
|
&issues_model.Reaction{UserID: u.ID},
|
||||||
&organization.TeamUser{UID: u.ID},
|
&organization.TeamUser{UID: u.ID},
|
||||||
&issues_model.Stopwatch{UserID: u.ID},
|
&issues_model.Stopwatch{UserID: u.ID},
|
||||||
|
|||||||
@@ -133,10 +133,6 @@
|
|||||||
<dd>{{svg (Iif .Service.AllowOnlyExternalRegistration "octicon-check" "octicon-x")}}</dd>
|
<dd>{{svg (Iif .Service.AllowOnlyExternalRegistration "octicon-check" "octicon-x")}}</dd>
|
||||||
<dt>{{ctx.Locale.Tr "admin.config.show_registration_button"}}</dt>
|
<dt>{{ctx.Locale.Tr "admin.config.show_registration_button"}}</dt>
|
||||||
<dd>{{svg (Iif .Service.ShowRegistrationButton "octicon-check" "octicon-x")}}</dd>
|
<dd>{{svg (Iif .Service.ShowRegistrationButton "octicon-check" "octicon-x")}}</dd>
|
||||||
<dt>{{ctx.Locale.Tr "admin.config.enable_openid_signup"}}</dt>
|
|
||||||
<dd>{{svg (Iif .Service.EnableOpenIDSignUp "octicon-check" "octicon-x")}}</dd>
|
|
||||||
<dt>{{ctx.Locale.Tr "admin.config.enable_openid_signin"}}</dt>
|
|
||||||
<dd>{{svg (Iif .Service.EnableOpenIDSignIn "octicon-check" "octicon-x")}}</dd>
|
|
||||||
<dt>{{ctx.Locale.Tr "admin.config.require_sign_in_view"}}</dt>
|
<dt>{{ctx.Locale.Tr "admin.config.require_sign_in_view"}}</dt>
|
||||||
<dd>{{svg (Iif .Service.RequireSignInViewStrict "octicon-check" "octicon-x")}}</dd>
|
<dd>{{svg (Iif .Service.RequireSignInViewStrict "octicon-check" "octicon-x")}}</dd>
|
||||||
<dt>{{ctx.Locale.Tr "admin.config.mail_notify"}}</dt>
|
<dt>{{ctx.Locale.Tr "admin.config.mail_notify"}}</dt>
|
||||||
|
|||||||
@@ -78,15 +78,6 @@
|
|||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{range .OpenIDs}}
|
|
||||||
{{if .Show}}
|
|
||||||
<div class="item">
|
|
||||||
{{svg "fontawesome-openid"}}
|
|
||||||
<a target="_blank" href="{{.URI}}">{{.URI}}</a>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
<div class="item">{{svg "octicon-calendar"}} <span>{{ctx.Locale.Tr "user.joined_on" (DateUtils.AbsoluteShort .ContextUser.CreatedUnix)}}</span></div>
|
<div class="item">{{svg "octicon-calendar"}} <span>{{ctx.Locale.Tr "user.joined_on" (DateUtils.AbsoluteShort .ContextUser.CreatedUnix)}}</span></div>
|
||||||
|
|
||||||
{{if and .Orgs .HasOrgsVisible}}
|
{{if and .Orgs .HasOrgsVisible}}
|
||||||
|
|||||||
@@ -4,11 +4,6 @@
|
|||||||
{{$provider.IconHTML 24}} {{ctx.Locale.Tr "sign_in_with_provider" $provider.DisplayName}}
|
{{$provider.IconHTML 24}} {{ctx.Locale.Tr "sign_in_with_provider" $provider.DisplayName}}
|
||||||
</a>
|
</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{if .EnableOpenIDSignIn}}
|
|
||||||
<a class="ui button external-login-link tw-gap-3" data-require-appurl-check="true" rel="nofollow" href="{{AppSubUrl}}/user/login/openid">
|
|
||||||
{{svg "fontawesome-openid" 24}} {{ctx.Locale.Tr "sign_in_with_provider" "OpenID"}}
|
|
||||||
</a>
|
|
||||||
{{end}}
|
|
||||||
{{if .EnableSSPI}}
|
{{if .EnableSSPI}}
|
||||||
<a class="ui button external-login-link tw-gap-3" rel="nofollow" href="{{AppSubUrl}}/user/login?auth_with_sspi=1">
|
<a class="ui button external-login-link tw-gap-3" rel="nofollow" href="{{AppSubUrl}}/user/login?auth_with_sspi=1">
|
||||||
{{svg "fontawesome-windows" 24}} Windows SSPI
|
{{svg "fontawesome-windows" 24}} Windows SSPI
|
||||||
|
|||||||
@@ -49,7 +49,7 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
</form>
|
</form>
|
||||||
{{end}}{{/*end if .EnablePasswordSignInForm*/}}
|
{{end}}{{/*end if .EnablePasswordSignInForm*/}}
|
||||||
{{$showExternalAuthMethods := or .OAuth2Providers .EnableOpenIDSignIn .EnableSSPI}}
|
{{$showExternalAuthMethods := or .OAuth2Providers .EnableSSPI}}
|
||||||
{{if and $showExternalAuthMethods .EnablePasswordSignInForm}}
|
{{if and $showExternalAuthMethods .EnablePasswordSignInForm}}
|
||||||
<div class="divider divider-text">{{ctx.Locale.Tr "sign_in_or"}}</div>
|
<div class="divider divider-text">{{ctx.Locale.Tr "sign_in_or"}}</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
{{template "base/head" .}}
|
|
||||||
<div role="main" aria-label="{{.Title}}" class="page-content user signin openid">
|
|
||||||
<div class="ui middle very relaxed page grid">
|
|
||||||
<div class="column tw-flex tw-flex-col tw-gap-4 tw-max-w-2xl tw-m-auto">
|
|
||||||
<a href="{{AppSubUrl}}/user/login" class="tw-mx-auto">
|
|
||||||
<img width="100" height="100" src="{{AssetUrlPrefix}}/img/logo.svg" alt="{{ctx.Locale.Tr "logo"}}">
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<div class="ui container fluid">
|
|
||||||
{{template "base/alert" .}}
|
|
||||||
<h4 class="ui top attached header center">
|
|
||||||
{{svg "fontawesome-openid"}}
|
|
||||||
OpenID
|
|
||||||
</h4>
|
|
||||||
<div class="ui attached segment">
|
|
||||||
<form class="ui form tw-m-auto" action="{{.Link}}" method="post">
|
|
||||||
<div class="inline field">
|
|
||||||
{{ctx.Locale.Tr "auth.openid_signin_desc"}}
|
|
||||||
</div>
|
|
||||||
<div class="required field {{if .Err_OpenID}}error{{end}}">
|
|
||||||
<label for="openid">
|
|
||||||
{{svg "fontawesome-openid"}}
|
|
||||||
OpenID URI
|
|
||||||
</label>
|
|
||||||
<input id="openid" name="openid" value="{{.openid}}" autofocus required>
|
|
||||||
</div>
|
|
||||||
<div class="inline field">
|
|
||||||
<div class="ui checkbox">
|
|
||||||
<label>{{ctx.Locale.Tr "auth.remember_me"}}</label>
|
|
||||||
<input name="remember" type="checkbox">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="inline field">
|
|
||||||
<button type="submit" class="ui primary button tw-w-full">{{ctx.Locale.Tr "sign_in"}}</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="ui container fluid">
|
|
||||||
{{template "user/auth/webauthn_error" .}}
|
|
||||||
|
|
||||||
<div class="ui attached segment header top tw-flex tw-flex-col tw-items-center">
|
|
||||||
<a href="{{AppSubUrl}}/user/login">{{ctx.Locale.Tr "auth.back_to_sign_in"}}</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{template "base/footer" .}}
|
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{$showExternalAuthMethods := or .OAuth2Providers .EnableOpenIDSignIn .EnableSSPI}}
|
{{$showExternalAuthMethods := or .OAuth2Providers .EnableSSPI}}
|
||||||
{{if $showExternalAuthMethods}}
|
{{if $showExternalAuthMethods}}
|
||||||
<div class="divider divider-text">{{ctx.Locale.Tr "sign_in_or"}}</div>
|
<div class="divider divider-text">{{ctx.Locale.Tr "sign_in_or"}}</div>
|
||||||
{{template "user/auth/external_auth_methods" .}}
|
{{template "user/auth/external_auth_methods" .}}
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
{{template "base/head" .}}
|
|
||||||
<div role="main" aria-label="{{.Title}}" class="page-content user signup">
|
|
||||||
{{template "user/auth/signup_openid_navbar" .}}
|
|
||||||
<div class="ui container medium-width">
|
|
||||||
{{template "base/alert" .}}
|
|
||||||
<h4 class="ui top attached header">
|
|
||||||
{{ctx.Locale.Tr "auth.openid_connect_title"}}
|
|
||||||
</h4>
|
|
||||||
<div class="ui attached segment">
|
|
||||||
<form class="ui form left-right-form" action="{{.Link}}" method="post">
|
|
||||||
<div class="inline field">
|
|
||||||
<span class="help">{{ctx.Locale.Tr "auth.openid_connect_desc"}}</span>
|
|
||||||
</div>
|
|
||||||
<div class="required inline field {{if .Err_UserName}}error{{end}}">
|
|
||||||
<label for="user_name">{{ctx.Locale.Tr "home.uname_holder"}}</label>
|
|
||||||
<input id="user_name" type="text" name="user_name" value="{{.user_name}}" autofocus required>
|
|
||||||
</div>
|
|
||||||
<div class="required inline field {{if .Err_Password}}error{{end}}">
|
|
||||||
<label for="password">{{ctx.Locale.Tr "password"}}</label>
|
|
||||||
<input id="password" name="password" type="password" value="{{.password}}" autocomplete="off" required>
|
|
||||||
</div>
|
|
||||||
<div class="inline field">
|
|
||||||
<label for="openid">OpenID URI</label>
|
|
||||||
<input id="openid" value="{{.OpenID}}" readonly>
|
|
||||||
</div>
|
|
||||||
<div class="inline field">
|
|
||||||
<label></label>
|
|
||||||
<button type="submit" class="ui primary button">{{ctx.Locale.Tr "auth.openid_connect_submit"}}</button>
|
|
||||||
<a href="{{AppSubUrl}}/user/forgot_password">{{ctx.Locale.Tr "auth.forgot_password"}}</a>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{template "base/footer" .}}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
<overflow-menu class="ui secondary pointing tabular top attached borderless menu secondary-nav">
|
|
||||||
<div class="overflow-menu-items tw-justify-center">
|
|
||||||
<a class="{{if .PageIsOpenIDConnect}}active {{end}}item" href="{{AppSubUrl}}/user/openid/connect">
|
|
||||||
{{ctx.Locale.Tr "auth.openid_connect_title"}}
|
|
||||||
</a>
|
|
||||||
{{if and .EnableOpenIDSignUp (not .AllowOnlyInternalRegistration)}}
|
|
||||||
<a class="{{if .PageIsOpenIDRegister}}active {{end}}item" href="{{AppSubUrl}}/user/openid/register">
|
|
||||||
{{ctx.Locale.Tr "auth.openid_register_title"}}
|
|
||||||
</a>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
</overflow-menu>
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
{{template "base/head" .}}
|
|
||||||
<div role="main" aria-label="{{.Title}}" class="page-content user signup">
|
|
||||||
{{template "user/auth/signup_openid_navbar" .}}
|
|
||||||
<div class="ui container">
|
|
||||||
{{template "base/alert" .}}
|
|
||||||
<h4 class="ui top attached header">
|
|
||||||
{{ctx.Locale.Tr "auth.openid_register_title"}}
|
|
||||||
</h4>
|
|
||||||
<div class="ui attached segment">
|
|
||||||
<p>
|
|
||||||
{{ctx.Locale.Tr "auth.openid_register_desc"}}
|
|
||||||
</p>
|
|
||||||
<form class="ui form" action="{{.Link}}" method="post">
|
|
||||||
<div class="required field {{if .Err_UserName}}error{{end}}">
|
|
||||||
<label for="user_name">{{ctx.Locale.Tr "username"}}</label>
|
|
||||||
<input id="user_name" type="text" name="user_name" value="{{.user_name}}" autofocus required>
|
|
||||||
</div>
|
|
||||||
<div class="required field {{if .Err_Email}}error{{end}}">
|
|
||||||
<label for="email">{{ctx.Locale.Tr "email"}}</label>
|
|
||||||
<input id="email" name="email" type="email" value="{{.email}}" required>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{template "user/auth/captcha" .}}
|
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<label for="openid">OpenID URI</label>
|
|
||||||
<input id="openid" value="{{.OpenID}}" readonly>
|
|
||||||
</div>
|
|
||||||
<div class="inline field">
|
|
||||||
<button type="submit" class="ui primary button">{{ctx.Locale.Tr "auth.create_new_account"}}</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{template "base/footer" .}}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
<h4 class="ui top attached header">
|
|
||||||
{{ctx.Locale.Tr "settings.manage_openid"}}
|
|
||||||
</h4>
|
|
||||||
<div class="ui attached segment">
|
|
||||||
<div class="flex-divided-list items-with-main">
|
|
||||||
<div class="item">
|
|
||||||
{{ctx.Locale.Tr "settings.openid_desc"}}
|
|
||||||
</div>
|
|
||||||
{{range .OpenIDs}}
|
|
||||||
<div class="item tw-items-center">
|
|
||||||
<div class="item-leading">
|
|
||||||
{{svg "fontawesome-openid" 20}}
|
|
||||||
</div>
|
|
||||||
<div class="item-main">
|
|
||||||
<div class="item-title">{{.URI}}</div>
|
|
||||||
</div>
|
|
||||||
<div class="item-trailing">
|
|
||||||
<form action="{{AppSubUrl}}/user/settings/security/openid/toggle_visibility" method="post">
|
|
||||||
<input name="id" type="hidden" value="{{.ID}}">
|
|
||||||
{{if .Show}}
|
|
||||||
<button type="submit" class="ui tiny button">
|
|
||||||
{{svg "octicon-eye" 16 "icon"}}
|
|
||||||
{{ctx.Locale.Tr "settings.hide_openid"}}
|
|
||||||
</button>
|
|
||||||
{{else}}
|
|
||||||
<button type="submit" class="ui tiny button">
|
|
||||||
{{svg "octicon-eye-closed" 16 "icon"}}
|
|
||||||
{{ctx.Locale.Tr "settings.show_openid"}}
|
|
||||||
</button>
|
|
||||||
{{end}}
|
|
||||||
</form>
|
|
||||||
<button type="button" class="ui red tiny button link-action" data-modal-confirm="#delete-openid" data-url="{{AppSubUrl}}/user/settings/security/openid/delete?id={{.ID}}">
|
|
||||||
{{ctx.Locale.Tr "remove"}}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="ui bottom attached segment">
|
|
||||||
<form class="ui form" action="{{AppSubUrl}}/user/settings/security/openid" method="post">
|
|
||||||
<div class="required field {{if .Err_OpenID}}error{{end}}">
|
|
||||||
<label for="openid">{{ctx.Locale.Tr "settings.add_new_openid"}}</label>
|
|
||||||
<input id="openid" name="openid" type="text" required>
|
|
||||||
</div>
|
|
||||||
<button type="submit" class="ui primary button">
|
|
||||||
{{ctx.Locale.Tr "settings.add_openid"}}
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<dialog class="modal-dialog" id="delete-openid">
|
|
||||||
<div class="header">
|
|
||||||
{{svg "octicon-trash"}}
|
|
||||||
{{ctx.Locale.Tr "settings.openid_deletion"}}
|
|
||||||
</div>
|
|
||||||
<div class="content">
|
|
||||||
<p>{{ctx.Locale.Tr "settings.openid_deletion_desc"}}</p>
|
|
||||||
</div>
|
|
||||||
{{template "base/modal_actions_confirm" .}}
|
|
||||||
</dialog>
|
|
||||||
</div>
|
|
||||||
@@ -7,9 +7,6 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
{{if not ($.UserDisabledFeatures.Contains "manage_credentials")}}
|
{{if not ($.UserDisabledFeatures.Contains "manage_credentials")}}
|
||||||
{{template "user/settings/security/accountlinks" .}}
|
{{template "user/settings/security/accountlinks" .}}
|
||||||
{{if .EnableOpenIDSignIn}}
|
|
||||||
{{template "user/settings/security/openid" .}}
|
|
||||||
{{end}}
|
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="448" height="448" viewBox="0 0 448 448">
|
|
||||||
<path d="M271.5 0v384l-68 32c-115-10.25-203.5-71.5-203.5-145.75 0-71.5 82.5-131 191.75-144.25v43c-71.5 12.5-124 53-124 101.25 0 51 58.5 93.25 135.75 103v0-340zM438.75 145.5l9.25 97.5-131.25-28.5 36.75-20.75c-19.5-11.5-43.5-20-70-24.75v-43c46.25 5.5 87.75 19.5 120.25 39.25z"></path>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 393 B |
Reference in New Issue
Block a user