2015-06-11 02:40:05 +00:00
|
|
|
package htpasswd
|
2015-04-21 19:57:12 +00:00
|
|
|
|
|
|
|
import (
|
2017-08-13 05:56:11 +00:00
|
|
|
"bytes"
|
2022-11-02 21:55:22 +00:00
|
|
|
"io"
|
2015-04-21 19:57:12 +00:00
|
|
|
"net/http"
|
|
|
|
"net/http/httptest"
|
2017-08-13 05:56:11 +00:00
|
|
|
"os"
|
2015-04-21 19:57:12 +00:00
|
|
|
"testing"
|
|
|
|
|
2023-10-24 17:16:58 +00:00
|
|
|
"github.com/distribution/distribution/v3/internal/dcontext"
|
2020-08-24 11:18:39 +00:00
|
|
|
"github.com/distribution/distribution/v3/registry/auth"
|
2015-04-21 19:57:12 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func TestBasicAccessController(t *testing.T) {
|
|
|
|
testRealm := "The-Shire"
|
2015-06-04 16:02:13 +00:00
|
|
|
testUsers := []string{"bilbo", "frodo", "MiShil", "DeokMan"}
|
|
|
|
testPasswords := []string{"baggins", "baggins", "새주", "공주님"}
|
2015-06-04 15:46:34 +00:00
|
|
|
testHtpasswdContent := `bilbo:{SHA}5siv5c0SHx681xU6GiSx9ZQryqs=
|
|
|
|
frodo:$2y$05$926C3y10Quzn/LnqQH86VOEVh/18T6RnLaS.khre96jLNL/7e.K5W
|
|
|
|
MiShil:$2y$05$0oHgwMehvoe8iAWS8I.7l.KoECXrwVaC16RPfaSCU5eVTFrATuMI2
|
|
|
|
DeokMan:공주님`
|
2015-04-21 19:57:12 +00:00
|
|
|
|
2022-11-02 21:55:22 +00:00
|
|
|
tempFile, err := os.CreateTemp("", "htpasswd-test")
|
2015-04-21 19:57:12 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal("could not create temporary htpasswd file")
|
|
|
|
}
|
|
|
|
if _, err = tempFile.WriteString(testHtpasswdContent); err != nil {
|
|
|
|
t.Fatal("could not write temporary htpasswd file")
|
|
|
|
}
|
|
|
|
|
|
|
|
options := map[string]interface{}{
|
|
|
|
"realm": testRealm,
|
|
|
|
"path": tempFile.Name(),
|
|
|
|
}
|
2023-10-24 17:16:58 +00:00
|
|
|
ctx := dcontext.Background()
|
2015-04-21 19:57:12 +00:00
|
|
|
|
|
|
|
accessController, err := newAccessController(options)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal("error creating access controller")
|
|
|
|
}
|
|
|
|
|
|
|
|
tempFile.Close()
|
2015-06-04 16:02:13 +00:00
|
|
|
|
2022-11-02 21:05:45 +00:00
|
|
|
userNumber := 0
|
2015-06-04 16:02:13 +00:00
|
|
|
|
2015-04-21 19:57:12 +00:00
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
2023-10-24 17:16:58 +00:00
|
|
|
ctx := dcontext.WithRequest(ctx, r)
|
2015-04-21 19:57:12 +00:00
|
|
|
authCtx, err := accessController.Authorized(ctx)
|
|
|
|
if err != nil {
|
|
|
|
switch err := err.(type) {
|
|
|
|
case auth.Challenge:
|
2018-09-20 21:53:34 +00:00
|
|
|
err.SetHeaders(r, w)
|
2015-06-17 01:57:47 +00:00
|
|
|
w.WriteHeader(http.StatusUnauthorized)
|
2015-04-21 19:57:12 +00:00
|
|
|
return
|
|
|
|
default:
|
|
|
|
t.Fatalf("unexpected error authorizing request: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-01-29 01:02:09 +00:00
|
|
|
userInfo, ok := authCtx.Value(auth.UserKey).(auth.UserInfo)
|
2015-04-21 19:57:12 +00:00
|
|
|
if !ok {
|
|
|
|
t.Fatal("basic accessController did not set auth.user context")
|
|
|
|
}
|
|
|
|
|
2015-06-04 15:46:34 +00:00
|
|
|
if userInfo.Name != testUsers[userNumber] {
|
|
|
|
t.Fatalf("expected user name %q, got %q", testUsers[userNumber], userInfo.Name)
|
2015-04-21 19:57:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
|
|
}))
|
|
|
|
|
|
|
|
client := &http.Client{
|
|
|
|
CheckRedirect: nil,
|
|
|
|
}
|
|
|
|
|
2022-11-02 22:31:23 +00:00
|
|
|
req, _ := http.NewRequest(http.MethodGet, server.URL, nil)
|
2015-04-21 19:57:12 +00:00
|
|
|
resp, err := client.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unexpected error during GET: %v", err)
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
// Request should not be authorized
|
|
|
|
if resp.StatusCode != http.StatusUnauthorized {
|
|
|
|
t.Fatalf("unexpected non-fail response status: %v != %v", resp.StatusCode, http.StatusUnauthorized)
|
|
|
|
}
|
|
|
|
|
2015-06-11 02:29:27 +00:00
|
|
|
nonbcrypt := map[string]struct{}{
|
2015-06-12 00:06:35 +00:00
|
|
|
"bilbo": {},
|
|
|
|
"DeokMan": {},
|
2015-06-11 02:29:27 +00:00
|
|
|
}
|
|
|
|
|
2015-06-04 15:46:34 +00:00
|
|
|
for i := 0; i < len(testUsers); i++ {
|
|
|
|
userNumber = i
|
2022-11-02 22:31:23 +00:00
|
|
|
req, err := http.NewRequest(http.MethodGet, server.URL, nil)
|
2015-06-09 01:56:48 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error allocating new request: %v", err)
|
|
|
|
}
|
2015-04-21 19:57:12 +00:00
|
|
|
|
2015-06-09 01:56:48 +00:00
|
|
|
req.SetBasicAuth(testUsers[i], testPasswords[i])
|
2015-06-04 16:02:13 +00:00
|
|
|
|
2015-06-09 01:56:48 +00:00
|
|
|
resp, err = client.Do(req)
|
2015-06-04 15:46:34 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("unexpected error during GET: %v", err)
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
2015-04-21 19:57:12 +00:00
|
|
|
|
2015-06-11 02:29:27 +00:00
|
|
|
if _, ok := nonbcrypt[testUsers[i]]; ok {
|
|
|
|
// these are not allowed.
|
|
|
|
// Request should be authorized
|
|
|
|
if resp.StatusCode != http.StatusUnauthorized {
|
|
|
|
t.Fatalf("unexpected non-success response status: %v != %v for %s %s", resp.StatusCode, http.StatusUnauthorized, testUsers[i], testPasswords[i])
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Request should be authorized
|
|
|
|
if resp.StatusCode != http.StatusNoContent {
|
|
|
|
t.Fatalf("unexpected non-success response status: %v != %v for %s %s", resp.StatusCode, http.StatusNoContent, testUsers[i], testPasswords[i])
|
|
|
|
}
|
2015-06-04 15:46:34 +00:00
|
|
|
}
|
2015-04-21 19:57:12 +00:00
|
|
|
}
|
|
|
|
}
|
2017-08-13 05:56:11 +00:00
|
|
|
|
|
|
|
func TestCreateHtpasswdFile(t *testing.T) {
|
2022-11-02 21:55:22 +00:00
|
|
|
tempFile, err := os.CreateTemp("", "htpasswd-test")
|
2017-08-13 05:56:11 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("could not create temporary htpasswd file %v", err)
|
|
|
|
}
|
|
|
|
defer tempFile.Close()
|
|
|
|
options := map[string]interface{}{
|
|
|
|
"realm": "/auth/htpasswd",
|
|
|
|
"path": tempFile.Name(),
|
|
|
|
}
|
|
|
|
// Ensure file is not populated
|
|
|
|
if _, err := newAccessController(options); err != nil {
|
|
|
|
t.Fatalf("error creating access controller %v", err)
|
|
|
|
}
|
2022-11-02 21:55:22 +00:00
|
|
|
content, err := io.ReadAll(tempFile)
|
2017-08-13 05:56:11 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("failed to read file %v", err)
|
|
|
|
}
|
|
|
|
if !bytes.Equal([]byte{}, content) {
|
|
|
|
t.Fatalf("htpasswd file should not be populated %v", string(content))
|
|
|
|
}
|
|
|
|
if err := os.Remove(tempFile.Name()); err != nil {
|
|
|
|
t.Fatalf("failed to remove temp file %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure htpasswd file is populated
|
|
|
|
if _, err := newAccessController(options); err != nil {
|
|
|
|
t.Fatalf("error creating access controller %v", err)
|
|
|
|
}
|
2022-11-02 21:55:22 +00:00
|
|
|
content, err = os.ReadFile(tempFile.Name())
|
2017-08-13 05:56:11 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("failed to read file %v", err)
|
|
|
|
}
|
|
|
|
if !bytes.HasPrefix(content, []byte("docker:$2a$")) {
|
|
|
|
t.Fatalf("failed to find default user in file %s", string(content))
|
|
|
|
}
|
|
|
|
}
|