photoprism/internal/server/webdav_auth_test.go
Michael Mayer a4e2bb33b9 2FA: Rename "Auth Secret" to "App Password" for more clarity #782 #808
Signed-off-by: Michael Mayer <michael@photoprism.app>
2024-01-19 18:10:01 +01:00

254 lines
7.6 KiB
Go

package server
import (
"encoding/base64"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/photoprism/photoprism/internal/acl"
"github.com/photoprism/photoprism/internal/config"
"github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/pkg/header"
"github.com/photoprism/photoprism/pkg/rnd"
)
func TestWebDAVAuth(t *testing.T) {
conf := config.TestConfig()
webdavHandler := WebDAVAuth(conf)
t.Run("Unauthorized", func(t *testing.T) {
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = &http.Request{
Header: make(http.Header),
}
webdavAuthCache.Flush()
webdavHandler(c)
assert.Equal(t, http.StatusUnauthorized, c.Writer.Status())
assert.Equal(t, BasicAuthRealm, c.Writer.Header().Get("WWW-Authenticate"))
})
t.Run("AliceToken", func(t *testing.T) {
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = &http.Request{
Header: make(http.Header),
}
webdavAuthCache.Flush()
sess := entity.SessionFixtures.Get("alice_token")
header.SetAuthorization(c.Request, sess.AuthToken())
webdavHandler(c)
assert.Equal(t, http.StatusOK, c.Writer.Status())
assert.Equal(t, "", c.Writer.Header().Get("WWW-Authenticate"))
})
t.Run("AliceTokenWebdav", func(t *testing.T) {
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = &http.Request{
Header: make(http.Header),
}
sess := entity.SessionFixtures.Get("alice_token_webdav")
basicAuth := []byte(fmt.Sprintf("alice:%s", sess.AuthToken()))
c.Request.Header.Add(header.Auth, fmt.Sprintf("%s %s", header.AuthBasic, base64.StdEncoding.EncodeToString(basicAuth)))
webdavAuthCache.Flush()
webdavHandler(c)
assert.Equal(t, http.StatusOK, c.Writer.Status())
assert.Equal(t, "", c.Writer.Header().Get("WWW-Authenticate"))
})
t.Run("AliceTokenWebdavWrongUsername", func(t *testing.T) {
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = &http.Request{
Header: make(http.Header),
}
sess := entity.SessionFixtures.Get("alice_token_webdav")
basicAuth := []byte(fmt.Sprintf("bob:%s", sess.AuthToken()))
c.Request.Header.Add(header.Auth, fmt.Sprintf("%s %s", header.AuthBasic, base64.StdEncoding.EncodeToString(basicAuth)))
webdavAuthCache.Flush()
webdavHandler(c)
assert.Equal(t, http.StatusUnauthorized, c.Writer.Status())
assert.Equal(t, BasicAuthRealm, c.Writer.Header().Get("WWW-Authenticate"))
})
t.Run("AliceTokenWebdavWithoutUsername", func(t *testing.T) {
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = &http.Request{
Header: make(http.Header),
}
sess := entity.SessionFixtures.Get("alice_token_webdav")
basicAuth := []byte(fmt.Sprintf(":%s", sess.AuthToken()))
c.Request.Header.Add(header.Auth, fmt.Sprintf("%s %s", header.AuthBasic, base64.StdEncoding.EncodeToString(basicAuth)))
webdavAuthCache.Flush()
webdavHandler(c)
assert.Equal(t, http.StatusUnauthorized, c.Writer.Status())
assert.Equal(t, BasicAuthRealm, c.Writer.Header().Get("WWW-Authenticate"))
})
t.Run("AliceTokenScope", func(t *testing.T) {
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = &http.Request{
Header: make(http.Header),
}
sess := entity.SessionFixtures.Get("alice_token_scope")
header.SetAuthorization(c.Request, sess.AuthToken())
webdavAuthCache.Flush()
webdavHandler(c)
assert.Equal(t, http.StatusUnauthorized, c.Writer.Status())
assert.Equal(t, BasicAuthRealm, c.Writer.Header().Get("WWW-Authenticate"))
})
t.Run("InvalidAuthToken", func(t *testing.T) {
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = &http.Request{
Header: make(http.Header),
}
header.SetAuthorization(c.Request, rnd.AuthToken())
webdavAuthCache.Flush()
webdavHandler(c)
assert.Equal(t, http.StatusUnauthorized, c.Writer.Status())
assert.Equal(t, BasicAuthRealm, c.Writer.Header().Get("WWW-Authenticate"))
})
t.Run("InvalidAppPassword", func(t *testing.T) {
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = &http.Request{
Header: make(http.Header),
}
header.SetAuthorization(c.Request, rnd.AppPassword())
webdavAuthCache.Flush()
webdavHandler(c)
assert.Equal(t, http.StatusUnauthorized, c.Writer.Status())
assert.Equal(t, BasicAuthRealm, c.Writer.Header().Get("WWW-Authenticate"))
})
}
func TestWebDAVAuthSession(t *testing.T) {
t.Run("AliceTokenWebdav", func(t *testing.T) {
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = &http.Request{
Header: make(http.Header),
}
s := entity.SessionFixtures.Get("alice_token_webdav")
// Get session with authorized user and webdav scope.
webdavAuthCache.Flush()
sess, user, sid, cached := WebDAVAuthSession(c, s.AuthToken())
// Check result.
assert.NotNil(t, sess)
assert.NotNil(t, user)
assert.True(t, sess.HasUser())
assert.Equal(t, user.UserUID, sess.UserUID)
assert.Equal(t, entity.UserFixtures.Get("alice").UserUID, sess.UserUID)
assert.True(t, sess.HasScope(acl.ResourceWebDAV.String()))
assert.False(t, cached)
assert.Equal(t, s.ID, sid)
assert.Equal(t, entity.UserFixtures.Get("alice").UserUID, user.UserUID)
assert.True(t, user.CanUseWebDAV())
// WebDAVAuthSession should not set a status code or any headers.
assert.Equal(t, http.StatusOK, c.Writer.Status())
assert.Equal(t, "", c.Writer.Header().Get("WWW-Authenticate"))
// Cache authentication.
webdavAuthCache.SetDefault(sid, user)
// Get cached user.
sess, user, sid, cached = WebDAVAuthSession(c, s.AuthToken())
// Check result.
assert.Nil(t, sess)
assert.NotNil(t, user)
assert.True(t, cached)
assert.Equal(t, s.ID, sid)
assert.Equal(t, entity.UserFixtures.Get("alice").UserUID, user.UserUID)
assert.True(t, user.CanUseWebDAV())
// WebDAVAuthSession should not set a status code or any headers.
assert.Equal(t, http.StatusOK, c.Writer.Status())
assert.Equal(t, "", c.Writer.Header().Get("WWW-Authenticate"))
})
t.Run("AliceTokenScope", func(t *testing.T) {
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = &http.Request{
Header: make(http.Header),
}
s := entity.SessionFixtures.Get("alice_token_scope")
// Get session without sufficient authorization scope.
sess, user, sid, cached := WebDAVAuthSession(c, s.AuthToken())
// Check result.
assert.NotNil(t, sess)
assert.NotNil(t, user)
assert.Equal(t, s.ID, sid)
assert.False(t, cached)
assert.True(t, sess.HasUser())
assert.Equal(t, user.UserUID, sess.UserUID)
assert.Equal(t, entity.UserFixtures.Get("alice").UserUID, user.UserUID)
assert.Equal(t, entity.UserFixtures.Get("alice").UserUID, sess.UserUID)
assert.True(t, user.CanUseWebDAV())
assert.False(t, sess.HasScope(acl.ResourceWebDAV.String()))
// WebDAVAuthSession should not set a status code or any headers.
assert.Equal(t, http.StatusOK, c.Writer.Status())
assert.Equal(t, "", c.Writer.Header().Get("WWW-Authenticate"))
})
t.Run("InvalidAppPassword", func(t *testing.T) {
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = &http.Request{
Header: make(http.Header),
}
appPassword := rnd.AppPassword()
authId := rnd.SessionID(appPassword)
// Get session with invalid app password.
sess, user, sid, cached := WebDAVAuthSession(c, appPassword)
// Check result.
assert.Nil(t, sess)
assert.Nil(t, user)
assert.Equal(t, authId, sid)
assert.False(t, cached)
// WebDAVAuthSession should not set a status code or any headers.
assert.Equal(t, http.StatusOK, c.Writer.Status())
assert.Equal(t, "", c.Writer.Header().Get("WWW-Authenticate"))
})
}