summaryrefslogtreecommitdiffstats
path: root/modules/password
diff options
context:
space:
mode:
Diffstat (limited to 'modules/password')
-rw-r--r--modules/password/password.go126
-rw-r--r--modules/password/password_test.go76
-rw-r--r--modules/password/pwn.go28
-rw-r--r--modules/password/pwn/pwn.go118
-rw-r--r--modules/password/pwn/pwn_test.go142
5 files changed, 0 insertions, 490 deletions
diff --git a/modules/password/password.go b/modules/password/password.go
deleted file mode 100644
index fe2a2a7bd5..0000000000
--- a/modules/password/password.go
+++ /dev/null
@@ -1,126 +0,0 @@
-// Copyright 2019 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package password
-
-import (
- "bytes"
- goContext "context"
- "crypto/rand"
- "math/big"
- "strings"
- "sync"
-
- "code.gitea.io/gitea/modules/context"
- "code.gitea.io/gitea/modules/setting"
-)
-
-// complexity contains information about a particular kind of password complexity
-type complexity struct {
- ValidChars string
- TrNameOne string
-}
-
-var (
- matchComplexityOnce sync.Once
- validChars string
- requiredList []complexity
-
- charComplexities = map[string]complexity{
- "lower": {
- `abcdefghijklmnopqrstuvwxyz`,
- "form.password_lowercase_one",
- },
- "upper": {
- `ABCDEFGHIJKLMNOPQRSTUVWXYZ`,
- "form.password_uppercase_one",
- },
- "digit": {
- `0123456789`,
- "form.password_digit_one",
- },
- "spec": {
- ` !"#$%&'()*+,-./:;<=>?@[\]^_{|}~` + "`",
- "form.password_special_one",
- },
- }
-)
-
-// NewComplexity for preparation
-func NewComplexity() {
- matchComplexityOnce.Do(func() {
- setupComplexity(setting.PasswordComplexity)
- })
-}
-
-func setupComplexity(values []string) {
- if len(values) != 1 || values[0] != "off" {
- for _, val := range values {
- if complex, ok := charComplexities[val]; ok {
- validChars += complex.ValidChars
- requiredList = append(requiredList, complex)
- }
- }
- if len(requiredList) == 0 {
- // No valid character classes found; use all classes as default
- for _, complex := range charComplexities {
- validChars += complex.ValidChars
- requiredList = append(requiredList, complex)
- }
- }
- }
- if validChars == "" {
- // No complexities to check; provide a sensible default for password generation
- validChars = charComplexities["lower"].ValidChars + charComplexities["upper"].ValidChars + charComplexities["digit"].ValidChars
- }
-}
-
-// IsComplexEnough return True if password meets complexity settings
-func IsComplexEnough(pwd string) bool {
- NewComplexity()
- if len(validChars) > 0 {
- for _, req := range requiredList {
- if !strings.ContainsAny(req.ValidChars, pwd) {
- return false
- }
- }
- }
- return true
-}
-
-// Generate a random password
-func Generate(n int) (string, error) {
- NewComplexity()
- buffer := make([]byte, n)
- max := big.NewInt(int64(len(validChars)))
- for {
- for j := 0; j < n; j++ {
- rnd, err := rand.Int(rand.Reader, max)
- if err != nil {
- return "", err
- }
- buffer[j] = validChars[rnd.Int64()]
- }
- pwned, err := IsPwned(goContext.Background(), string(buffer))
- if err != nil {
- return "", err
- }
- if IsComplexEnough(string(buffer)) && !pwned && string(buffer[0]) != " " && string(buffer[n-1]) != " " {
- return string(buffer), nil
- }
- }
-}
-
-// BuildComplexityError builds the error message when password complexity checks fail
-func BuildComplexityError(ctx *context.Context) string {
- var buffer bytes.Buffer
- buffer.WriteString(ctx.Tr("form.password_complexity"))
- buffer.WriteString("<ul>")
- for _, c := range requiredList {
- buffer.WriteString("<li>")
- buffer.WriteString(ctx.Tr(c.TrNameOne))
- buffer.WriteString("</li>")
- }
- buffer.WriteString("</ul>")
- return buffer.String()
-}
diff --git a/modules/password/password_test.go b/modules/password/password_test.go
deleted file mode 100644
index 6c35dc86bd..0000000000
--- a/modules/password/password_test.go
+++ /dev/null
@@ -1,76 +0,0 @@
-// Copyright 2019 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package password
-
-import (
- "testing"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestComplexity_IsComplexEnough(t *testing.T) {
- matchComplexityOnce.Do(func() {})
-
- testlist := []struct {
- complexity []string
- truevalues []string
- falsevalues []string
- }{
- {[]string{"off"}, []string{"1", "-", "a", "A", "ñ", "日本語"}, []string{}},
- {[]string{"lower"}, []string{"abc", "abc!"}, []string{"ABC", "123", "=!$", ""}},
- {[]string{"upper"}, []string{"ABC"}, []string{"abc", "123", "=!$", "abc!", ""}},
- {[]string{"digit"}, []string{"123"}, []string{"abc", "ABC", "=!$", "abc!", ""}},
- {[]string{"spec"}, []string{"=!$", "abc!"}, []string{"abc", "ABC", "123", ""}},
- {[]string{"off"}, []string{"abc", "ABC", "123", "=!$", "abc!", ""}, nil},
- {[]string{"lower", "spec"}, []string{"abc!"}, []string{"abc", "ABC", "123", "=!$", "abcABC123", ""}},
- {[]string{"lower", "upper", "digit"}, []string{"abcABC123"}, []string{"abc", "ABC", "123", "=!$", "abc!", ""}},
- {[]string{""}, []string{"abC=1", "abc!9D"}, []string{"ABC", "123", "=!$", ""}},
- }
-
- for _, test := range testlist {
- testComplextity(test.complexity)
- for _, val := range test.truevalues {
- assert.True(t, IsComplexEnough(val))
- }
- for _, val := range test.falsevalues {
- assert.False(t, IsComplexEnough(val))
- }
- }
-
- // Remove settings for other tests
- testComplextity([]string{"off"})
-}
-
-func TestComplexity_Generate(t *testing.T) {
- matchComplexityOnce.Do(func() {})
-
- const maxCount = 50
- const pwdLen = 50
-
- test := func(t *testing.T, modes []string) {
- testComplextity(modes)
- for i := 0; i < maxCount; i++ {
- pwd, err := Generate(pwdLen)
- assert.NoError(t, err)
- assert.Len(t, pwd, pwdLen)
- assert.True(t, IsComplexEnough(pwd), "Failed complexities with modes %+v for generated: %s", modes, pwd)
- }
- }
-
- test(t, []string{"lower"})
- test(t, []string{"upper"})
- test(t, []string{"lower", "upper", "spec"})
- test(t, []string{"off"})
- test(t, []string{""})
-
- // Remove settings for other tests
- testComplextity([]string{"off"})
-}
-
-func testComplextity(values []string) {
- // Cleanup previous values
- validChars = ""
- requiredList = make([]complexity, 0, len(values))
- setupComplexity(values)
-}
diff --git a/modules/password/pwn.go b/modules/password/pwn.go
deleted file mode 100644
index 91bad0d25b..0000000000
--- a/modules/password/pwn.go
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2020 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package password
-
-import (
- "context"
-
- "code.gitea.io/gitea/modules/password/pwn"
- "code.gitea.io/gitea/modules/setting"
-)
-
-// IsPwned checks whether a password has been pwned
-// NOTE: This func returns true if it encounters an error under the assumption that you ALWAYS want to check against
-// HIBP, so not getting a response should block a password until it can be verified.
-func IsPwned(ctx context.Context, password string) (bool, error) {
- if !setting.PasswordCheckPwn {
- return false, nil
- }
-
- client := pwn.New(pwn.WithContext(ctx))
- count, err := client.CheckPassword(password, true)
- if err != nil {
- return true, err
- }
-
- return count > 0, nil
-}
diff --git a/modules/password/pwn/pwn.go b/modules/password/pwn/pwn.go
deleted file mode 100644
index b5a015fb9c..0000000000
--- a/modules/password/pwn/pwn.go
+++ /dev/null
@@ -1,118 +0,0 @@
-// Copyright 2023 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package pwn
-
-import (
- "context"
- "crypto/sha1"
- "encoding/hex"
- "errors"
- "fmt"
- "io"
- "net/http"
- "strconv"
- "strings"
-
- "code.gitea.io/gitea/modules/setting"
-)
-
-const passwordURL = "https://api.pwnedpasswords.com/range/"
-
-// ErrEmptyPassword is an empty password error
-var ErrEmptyPassword = errors.New("password cannot be empty")
-
-// Client is a HaveIBeenPwned client
-type Client struct {
- ctx context.Context
- http *http.Client
-}
-
-// New returns a new HaveIBeenPwned Client
-func New(options ...ClientOption) *Client {
- client := &Client{
- ctx: context.Background(),
- http: http.DefaultClient,
- }
-
- for _, opt := range options {
- opt(client)
- }
-
- return client
-}
-
-// ClientOption is a way to modify a new Client
-type ClientOption func(*Client)
-
-// WithHTTP will set the http.Client of a Client
-func WithHTTP(httpClient *http.Client) func(pwnClient *Client) {
- return func(pwnClient *Client) {
- pwnClient.http = httpClient
- }
-}
-
-// WithContext will set the context.Context of a Client
-func WithContext(ctx context.Context) func(pwnClient *Client) {
- return func(pwnClient *Client) {
- pwnClient.ctx = ctx
- }
-}
-
-func newRequest(ctx context.Context, method, url string, body io.ReadCloser) (*http.Request, error) {
- req, err := http.NewRequestWithContext(ctx, method, url, body)
- if err != nil {
- return nil, err
- }
- req.Header.Add("User-Agent", "Gitea "+setting.AppVer)
- return req, nil
-}
-
-// CheckPassword returns the number of times a password has been compromised
-// Adding padding will make requests more secure, however is also slower
-// because artificial responses will be added to the response
-// For more information, see https://www.troyhunt.com/enhancing-pwned-passwords-privacy-with-padding/
-func (c *Client) CheckPassword(pw string, padding bool) (int, error) {
- if strings.TrimSpace(pw) == "" {
- return -1, ErrEmptyPassword
- }
-
- sha := sha1.New()
- sha.Write([]byte(pw))
- enc := hex.EncodeToString(sha.Sum(nil))
- prefix, suffix := enc[:5], enc[5:]
-
- req, err := newRequest(c.ctx, http.MethodGet, fmt.Sprintf("%s%s", passwordURL, prefix), nil)
- if err != nil {
- return -1, nil
- }
- if padding {
- req.Header.Add("Add-Padding", "true")
- }
-
- resp, err := c.http.Do(req)
- if err != nil {
- return -1, err
- }
-
- body, err := io.ReadAll(resp.Body)
- if err != nil {
- return -1, err
- }
- defer resp.Body.Close()
-
- for _, pair := range strings.Split(string(body), "\n") {
- parts := strings.Split(pair, ":")
- if len(parts) != 2 {
- continue
- }
- if strings.EqualFold(suffix, parts[0]) {
- count, err := strconv.ParseInt(strings.TrimSpace(parts[1]), 10, 64)
- if err != nil {
- return -1, err
- }
- return int(count), nil
- }
- }
- return 0, nil
-}
diff --git a/modules/password/pwn/pwn_test.go b/modules/password/pwn/pwn_test.go
deleted file mode 100644
index 148208b964..0000000000
--- a/modules/password/pwn/pwn_test.go
+++ /dev/null
@@ -1,142 +0,0 @@
-// Copyright 2023 The Gitea Authors. All rights reserved.
-// SPDX-License-Identifier: MIT
-
-package pwn
-
-import (
- "errors"
- "math/rand"
- "net/http"
- "os"
- "strings"
- "testing"
- "time"
-)
-
-var client = New(WithHTTP(&http.Client{
- Timeout: time.Second * 2,
-}))
-
-func TestMain(m *testing.M) {
- rand.Seed(time.Now().Unix())
- os.Exit(m.Run())
-}
-
-func TestPassword(t *testing.T) {
- // Check input error
- _, err := client.CheckPassword("", false)
- if err == nil {
- t.Log("blank input should return an error")
- t.Fail()
- }
- if !errors.Is(err, ErrEmptyPassword) {
- t.Log("blank input should return ErrEmptyPassword")
- t.Fail()
- }
-
- // Should fail
- fail := "password1234"
- count, err := client.CheckPassword(fail, false)
- if err != nil {
- t.Log(err)
- t.Fail()
- }
- if count == 0 {
- t.Logf("%s should fail as a password\n", fail)
- t.Fail()
- }
-
- // Should fail (with padding)
- failPad := "administrator"
- count, err = client.CheckPassword(failPad, true)
- if err != nil {
- t.Log(err)
- t.Fail()
- }
- if count == 0 {
- t.Logf("%s should fail as a password\n", failPad)
- t.Fail()
- }
-
- // Checking for a "good" password isn't going to be perfect, but we can give it a good try
- // with hopefully minimal error. Try five times?
- var good bool
- var pw string
- for idx := 0; idx <= 5; idx++ {
- pw = testPassword()
- count, err = client.CheckPassword(pw, false)
- if err != nil {
- t.Log(err)
- t.Fail()
- }
- if count == 0 {
- good = true
- break
- }
- }
- if !good {
- t.Log("no generated passwords passed. there is a chance this is a fluke")
- t.Fail()
- }
-
- // Again, but with padded responses
- good = false
- for idx := 0; idx <= 5; idx++ {
- pw = testPassword()
- count, err = client.CheckPassword(pw, true)
- if err != nil {
- t.Log(err)
- t.Fail()
- }
- if count == 0 {
- good = true
- break
- }
- }
- if !good {
- t.Log("no generated passwords passed. there is a chance this is a fluke")
- t.Fail()
- }
-}
-
-// Credit to https://golangbyexample.com/generate-random-password-golang/
-// DO NOT USE THIS FOR AN ACTUAL PASSWORD GENERATOR
-var (
- lowerCharSet = "abcdedfghijklmnopqrst"
- upperCharSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
- specialCharSet = "!@#$%&*"
- numberSet = "0123456789"
- allCharSet = lowerCharSet + upperCharSet + specialCharSet + numberSet
-)
-
-func testPassword() string {
- var password strings.Builder
-
- // Set special character
- for i := 0; i < 5; i++ {
- random := rand.Intn(len(specialCharSet))
- password.WriteString(string(specialCharSet[random]))
- }
-
- // Set numeric
- for i := 0; i < 5; i++ {
- random := rand.Intn(len(numberSet))
- password.WriteString(string(numberSet[random]))
- }
-
- // Set uppercase
- for i := 0; i < 5; i++ {
- random := rand.Intn(len(upperCharSet))
- password.WriteString(string(upperCharSet[random]))
- }
-
- for i := 0; i < 5; i++ {
- random := rand.Intn(len(allCharSet))
- password.WriteString(string(allCharSet[random]))
- }
- inRune := []rune(password.String())
- rand.Shuffle(len(inRune), func(i, j int) {
- inRune[i], inRune[j] = inRune[j], inRune[i]
- })
- return string(inRune)
-}