Skip to content

Commit

Permalink
Merge branch 'main' into feature-commit-cross-reference
Browse files Browse the repository at this point in the history
  • Loading branch information
lunny committed Jan 30, 2023
2 parents 7e695ae + 3ff5a6a commit 3e692bd
Show file tree
Hide file tree
Showing 16 changed files with 649 additions and 65 deletions.
10 changes: 0 additions & 10 deletions assets/go-licenses.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions build/generate-go-licenses.go
Expand Up @@ -9,6 +9,7 @@ import (
"encoding/json"
"io/fs"
"os"
goPath "path"
"path/filepath"
"regexp"
"sort"
Expand Down Expand Up @@ -47,13 +48,15 @@ func main() {

entries := []LicenseEntry{}
for _, path := range paths {
path := filepath.ToSlash(path)

licenseText, err := os.ReadFile(path)
if err != nil {
panic(err)
}

path := strings.Replace(path, base+string(os.PathSeparator), "", 1)
name := filepath.Dir(path)
path = strings.Replace(path, base+"/", "", 1)
name := goPath.Dir(path)

// There might be a bug somewhere in go-licenses that sometimes interprets the
// root package as "." and sometimes as "code.gitea.io/gitea". Workaround by
Expand Down
2 changes: 0 additions & 2 deletions go.mod
Expand Up @@ -96,8 +96,6 @@ require (
github.com/yuin/goldmark v1.5.3
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20220924101305-151362477c87
github.com/yuin/goldmark-meta v1.1.0
go.jolheiser.com/hcaptcha v0.0.4
go.jolheiser.com/pwn v0.0.3
golang.org/x/crypto v0.4.0
golang.org/x/net v0.4.0
golang.org/x/oauth2 v0.3.0
Expand Down
4 changes: 0 additions & 4 deletions go.sum
Expand Up @@ -1267,10 +1267,6 @@ go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
go.jolheiser.com/hcaptcha v0.0.4 h1:RrDERcr/Tz/kWyJenjVtI+V09RtLinXxlAemiwN5F+I=
go.jolheiser.com/hcaptcha v0.0.4/go.mod h1:aw32WQOxnQZ6E06C0LypCf+sxNxPACyOnq+ZGnrIYho=
go.jolheiser.com/pwn v0.0.3 h1:MQowb3QvCL5r5NmHmCPxw93SdjfgJ0q6rAwYn4i1Hjg=
go.jolheiser.com/pwn v0.0.3/go.mod h1:/j5Dl8ftNqqJ8Dlx3YTrJV1wIR2lWOTyrNU3Qe7rk6I=
go.mongodb.org/mongo-driver v1.7.3/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R79oO62zWg=
go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng=
go.mongodb.org/mongo-driver v1.8.3/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY=
Expand Down
47 changes: 47 additions & 0 deletions modules/hcaptcha/error.go
@@ -0,0 +1,47 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package hcaptcha

const (
ErrMissingInputSecret ErrorCode = "missing-input-secret"
ErrInvalidInputSecret ErrorCode = "invalid-input-secret"
ErrMissingInputResponse ErrorCode = "missing-input-response"
ErrInvalidInputResponse ErrorCode = "invalid-input-response"
ErrBadRequest ErrorCode = "bad-request"
ErrInvalidOrAlreadySeenResponse ErrorCode = "invalid-or-already-seen-response"
ErrNotUsingDummyPasscode ErrorCode = "not-using-dummy-passcode"
ErrSitekeySecretMismatch ErrorCode = "sitekey-secret-mismatch"
)

// ErrorCode is any possible error from hCaptcha
type ErrorCode string

// String fulfills the Stringer interface
func (err ErrorCode) String() string {
switch err {
case ErrMissingInputSecret:
return "Your secret key is missing."
case ErrInvalidInputSecret:
return "Your secret key is invalid or malformed."
case ErrMissingInputResponse:
return "The response parameter (verification token) is missing."
case ErrInvalidInputResponse:
return "The response parameter (verification token) is invalid or malformed."
case ErrBadRequest:
return "The request is invalid or malformed."
case ErrInvalidOrAlreadySeenResponse:
return "The response parameter has already been checked, or has another issue."
case ErrNotUsingDummyPasscode:
return "You have used a testing sitekey but have not used its matching secret."
case ErrSitekeySecretMismatch:
return "The sitekey is not registered with the provided secret."
default:
return ""
}
}

// Error fulfills the error interface
func (err ErrorCode) Error() string {
return err.String()
}
115 changes: 111 additions & 4 deletions modules/hcaptcha/hcaptcha.go
Expand Up @@ -5,20 +5,127 @@ package hcaptcha

import (
"context"
"io"
"net/http"
"net/url"
"strings"

"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/setting"

"go.jolheiser.com/hcaptcha"
)

const verifyURL = "https://hcaptcha.com/siteverify"

// Client is an hCaptcha client
type Client struct {
ctx context.Context
http *http.Client

secret string
}

// PostOptions are optional post form values
type PostOptions struct {
RemoteIP string
Sitekey string
}

// ClientOption is a func to modify a new Client
type ClientOption func(*Client)

// WithHTTP sets the http.Client of a Client
func WithHTTP(httpClient *http.Client) func(*Client) {
return func(hClient *Client) {
hClient.http = httpClient
}
}

// WithContext sets the context.Context of a Client
func WithContext(ctx context.Context) func(*Client) {
return func(hClient *Client) {
hClient.ctx = ctx
}
}

// New returns a new hCaptcha Client
func New(secret string, options ...ClientOption) (*Client, error) {
if strings.TrimSpace(secret) == "" {
return nil, ErrMissingInputSecret
}

client := &Client{
ctx: context.Background(),
http: http.DefaultClient,
secret: secret,
}

for _, opt := range options {
opt(client)
}

return client, nil
}

// Response is an hCaptcha response
type Response struct {
Success bool `json:"success"`
ChallengeTS string `json:"challenge_ts"`
Hostname string `json:"hostname"`
Credit bool `json:"credit,omitempty"`
ErrorCodes []ErrorCode `json:"error-codes"`
}

// Verify checks the response against the hCaptcha API
func (c *Client) Verify(token string, opts PostOptions) (*Response, error) {
if strings.TrimSpace(token) == "" {
return nil, ErrMissingInputResponse
}

post := url.Values{
"secret": []string{c.secret},
"response": []string{token},
}
if strings.TrimSpace(opts.RemoteIP) != "" {
post.Add("remoteip", opts.RemoteIP)
}
if strings.TrimSpace(opts.Sitekey) != "" {
post.Add("sitekey", opts.Sitekey)
}

// Basically a copy of http.PostForm, but with a context
req, err := http.NewRequestWithContext(c.ctx, http.MethodPost, verifyURL, strings.NewReader(post.Encode()))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

resp, err := c.http.Do(req)
if err != nil {
return nil, err
}

body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
defer resp.Body.Close()

var response *Response
if err := json.Unmarshal(body, &response); err != nil {
return nil, err
}

return response, nil
}

// Verify calls hCaptcha API to verify token
func Verify(ctx context.Context, response string) (bool, error) {
client, err := hcaptcha.New(setting.Service.HcaptchaSecret, hcaptcha.WithContext(ctx))
client, err := New(setting.Service.HcaptchaSecret, WithContext(ctx))
if err != nil {
return false, err
}

resp, err := client.Verify(response, hcaptcha.PostOptions{
resp, err := client.Verify(response, PostOptions{
Sitekey: setting.Service.HcaptchaSitekey,
})
if err != nil {
Expand Down
106 changes: 106 additions & 0 deletions modules/hcaptcha/hcaptcha_test.go
@@ -0,0 +1,106 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package hcaptcha

import (
"net/http"
"os"
"strings"
"testing"
"time"
)

const (
dummySiteKey = "10000000-ffff-ffff-ffff-000000000001"
dummySecret = "0x0000000000000000000000000000000000000000"
dummyToken = "10000000-aaaa-bbbb-cccc-000000000001"
)

func TestMain(m *testing.M) {
os.Exit(m.Run())
}

func TestCaptcha(t *testing.T) {
tt := []struct {
Name string
Secret string
Token string
Error ErrorCode
}{
{
Name: "Success",
Secret: dummySecret,
Token: dummyToken,
},
{
Name: "Missing Secret",
Token: dummyToken,
Error: ErrMissingInputSecret,
},
{
Name: "Missing Token",
Secret: dummySecret,
Error: ErrMissingInputResponse,
},
{
Name: "Invalid Token",
Secret: dummySecret,
Token: "test",
Error: ErrInvalidInputResponse,
},
}

for _, tc := range tt {
t.Run(tc.Name, func(t *testing.T) {
client, err := New(tc.Secret, WithHTTP(&http.Client{
Timeout: time.Second * 5,
}))
if err != nil {
// The only error that can be returned from creating a client
if tc.Error == ErrMissingInputSecret && err == ErrMissingInputSecret {
return
}
t.Log(err)
t.FailNow()
}

resp, err := client.Verify(tc.Token, PostOptions{
Sitekey: dummySiteKey,
})
if err != nil {
// The only error that can be returned prior to the request
if tc.Error == ErrMissingInputResponse && err == ErrMissingInputResponse {
return
}
t.Log(err)
t.FailNow()
}

if tc.Error.String() != "" {
if resp.Success {
t.Log("Verification should fail.")
t.Fail()
}
if len(resp.ErrorCodes) == 0 {
t.Log("hCaptcha should have returned an error.")
t.Fail()
}
var hasErr bool
for _, err := range resp.ErrorCodes {
if strings.EqualFold(err.String(), tc.Error.String()) {
hasErr = true
break
}
}
if !hasErr {
t.Log("hCaptcha did not return the error being tested")
t.Fail()
}
} else if !resp.Success {
t.Log("Verification should succeed.")
t.Fail()
}
})
}
}
3 changes: 1 addition & 2 deletions modules/password/pwn.go
Expand Up @@ -6,9 +6,8 @@ package password
import (
"context"

"code.gitea.io/gitea/modules/password/pwn"
"code.gitea.io/gitea/modules/setting"

"go.jolheiser.com/pwn"
)

// IsPwned checks whether a password has been pwned
Expand Down

0 comments on commit 3e692bd

Please sign in to comment.