chore: initialize Gitea v1.27.2 fork
giteabot backport / giteabot (push) Canceled after 0s
giteabot / giteabot (push) Canceled after 0s
release-nightly / nightly-binary (push) Canceled after 0s
release-nightly / nightly-container (push) Canceled after 0s
cache-seeder / gobuild (push) Canceled after 0s
cache-seeder / lint (bindata, lint-backend) (push) Canceled after 0s
release-nightly-snapcraft / build-and-publish (push) Canceled after 0s

Includes direct password setup links in registration emails.

Assisted-by: Codex:GPT-5
This commit is contained in:
2026-08-15 22:56:18 +08:00
commit e4afba3416
6210 changed files with 802505 additions and 0 deletions
+57
View File
@@ -0,0 +1,57 @@
// 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
}
@@ -0,0 +1,49 @@
// 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"))
}
+55
View File
@@ -0,0 +1,55 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package openid
import (
"net/http"
"sync"
"time"
"gitea.dev/modules/hostmatcher"
"gitea.dev/modules/proxy"
"gitea.dev/modules/setting"
"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 does discovery/verification via an SSRF-protected client, so a user-supplied
// OpenID identifier can't reach internal/loopback/reserved addresses. It honors the operator's
// [security] ALLOWED_HOST_LIST (empty defaults to "external"), matching the avatar/webhook/migration
// clients, and validates the proxy path too. Lazy: reads proxy/settings once.
openIDInstance = sync.OnceValue(func() *openid.OpenID {
allowList := hostmatcher.ParseHostMatchList("security.ALLOWED_HOST_LIST", setting.Security.AllowedHostList)
return openid.NewOpenID(&http.Client{
Timeout: 30 * time.Second,
Transport: hostmatcher.NewHTTPTransport("openid", allowList, nil, proxy.Proxy(), setting.Proxy.ProxyURLFixed, nil),
})
})
)
// 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)
}
+29
View File
@@ -0,0 +1,29 @@
// 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")
}