Auth: Add Tests #782

This commit is contained in:
Timo Volkmann 2021-09-09 11:08:45 +02:00
parent dea527591e
commit 9bdc38be9a
6 changed files with 161 additions and 36 deletions

View file

@ -18,8 +18,10 @@ import (
// NewApiTest returns new API test helper.
func NewApiTest() (app *gin.Engine, router *gin.RouterGroup, conf *config.Config) {
conf = service.Config()
gin.SetMode(gin.TestMode)
app = gin.New()
app.LoadHTMLGlob(conf.TemplatesPath() + "/*")
router = app.Group("/api/v1")
return app, router, service.Config()
}
@ -56,6 +58,15 @@ func PerformRequest(r http.Handler, method, path string) *httptest.ResponseRecor
return w
}
// Performs API request with empty request body and Cookie.
func PerformRequestWithCookie(r http.Handler, method, path string, cookie string) *httptest.ResponseRecorder {
req := httptest.NewRequest(method, path, nil)
req.Header.Add("Cookie", cookie)
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
return w
}
// Performs authenticated API request with empty request body.
func AuthenticatedRequest(r http.Handler, method, path, sess string) *httptest.ResponseRecorder {
req, _ := http.NewRequest(method, path, nil)

View file

@ -2,6 +2,7 @@ package api
import (
"net/http"
"strings"
"github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/entity"
@ -22,9 +23,6 @@ func AuthEndpoints(router *gin.RouterGroup) {
router.GET("/auth/external", func(c *gin.Context) {
handle := openIdConnect.AuthUrlHandler()
handle(c.Writer, c.Request)
//url := openIdConnect.AuthUrl()
//log.Debugf("Step1 - Get AuthCode: %q", url)
//c.Redirect(http.StatusFound, url)
return
})
@ -34,25 +32,33 @@ func AuthEndpoints(router *gin.RouterGroup) {
log.Errorf("%s", err)
return
}
var uname string
if len(userInfo.GetPreferredUsername()) >= 4 {
uname = userInfo.GetPreferredUsername()
} else if len(userInfo.GetNickname()) >= 4 {
uname = userInfo.GetNickname()
} else if len(userInfo.GetName()) >= 4 {
uname = strings.ReplaceAll(strings.ToLower(userInfo.GetName())," ", "-")
} else if len(userInfo.GetEmail()) >= 4 {
uname = userInfo.GetEmail()
} else {
log.Error("auth: no username found")
}
u := &entity.User{
FullName: userInfo.GetName(),
UserName: userInfo.GetPreferredUsername(),
UserName: uname,
PrimaryEmail: userInfo.GetEmail(),
ExternalID: userInfo.GetSubject(),
RoleAdmin: true,
}
log.Debugf("USER:\nfn: %s\nun: %s\npe: %s\nei: %s\n", u.FullName, u.UserName, u.PrimaryEmail, u.ExternalID)
//err = u.Validate()
//if err != nil {
// CallbackError(c, err.Error(), http.StatusInternalServerError)
// return
//}
log.Debugf("USER: %s %s %s %s\n", u.FullName, u.UserName, u.PrimaryEmail, u.ExternalID)
user, e := entity.CreateOrUpdateExternalUser(u)
if e != nil {
c.Error(e)
CallbackError(c, e.Error(), http.StatusInternalServerError)
callbackError(c, e.Error(), http.StatusInternalServerError)
return
}
log.Infof("user '%s' logged in", user.UserName)
@ -70,7 +76,7 @@ func AuthEndpoints(router *gin.RouterGroup) {
})
}
func CallbackError(c *gin.Context, err string, status int) {
func callbackError(c *gin.Context, err string, status int) {
c.Abort()
c.HTML(status, "callback.tmpl", gin.H{
"status": "error",

93
internal/api/auth_test.go Normal file
View file

@ -0,0 +1,93 @@
package api
import (
"net/http"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestAuthEndpoints(t *testing.T) {
t.Run("successful oidc authentication", func(t *testing.T) {
app, router, _ := NewApiTest()
AuthEndpoints(router)
// Step 1a: Request AuthURL
log.Debug("Requesting OIDC AuthURL...")
r := PerformRequest(app, http.MethodGet, "/api/v1/auth/external")
assert.Equal(t, http.StatusFound, r.Code)
// Step 1b: Redirect user agent to OP and save state cookie
l := r.Header().Get("Location")
log.Debug("Requesting AuthCode from OP: ", l)
cookie := r.Header().Get("Set-Cookie")
log.Debug("Cookie: ", cookie)
assert.Contains(t, l, "authorize")
var l2 string
cl := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
if strings.Contains(req.URL.String(), "localhost") {
l2 = req.URL.RequestURI()
return http.ErrUseLastResponse
}
return nil
},
}
_, err := cl.Get(l)
if err != nil {
t.Error(err)
}
log.Debug(l2)
log.Debug("Successful")
// Step 2a: OP redirects user agent back to PhotoPrism
// Step 2b: PhotoPrism redeems AuthCode and fetches tokens from OP
log.Debug("Redeem AuthCode...")
r3 := PerformRequestWithCookie(app, http.MethodGet, l2, cookie)
assert.Equal(t, r3.Code, http.StatusOK)
log.Debug("Successful")
})
t.Run("oidc authentication: missing cookie", func(t *testing.T) {
app, router, _ := NewApiTest()
AuthEndpoints(router)
// Step 1a: Request AuthURL
log.Debug("Requesting OIDC AuthURL...")
r := PerformRequest(app, http.MethodGet, "/api/v1/auth/external")
assert.Equal(t, r.Code, http.StatusFound)
// Step 1b: Redirect user agent to OP and save state cookie
l := r.Header().Get("Location")
log.Debug("Requesting AuthCode from OP: ", l)
cookie := ""
assert.Contains(t, l, "authorize")
var l2 string
cl := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
if strings.Contains(req.URL.String(), "localhost") {
l2 = req.URL.RequestURI()
return http.ErrUseLastResponse
}
return nil
},
}
_, err := cl.Get(l)
if err != nil {
t.Error(err)
}
log.Debug(l2)
log.Debug("Successful")
// Step 2a: OP redirects user agent back to PhotoPrism
// Step 2b: PhotoPrism redeems AuthCode and fetches tokens from OP
log.Debug("Redeem AuthCode...")
r3 := PerformRequestWithCookie(app, http.MethodGet, l2, cookie)
assert.Equal(t, http.StatusUnauthorized, r3.Code)
log.Debug("Successful")
})
}

View file

@ -52,28 +52,31 @@ func NewTestOptions() *Options {
}
c := &Options{
Name: "PhotoPrism",
Version: "0.0.0",
Copyright: "(c) 2018-2021 Michael Mayer",
Debug: true,
Public: true,
Experimental: true,
ReadOnly: false,
DetectNSFW: true,
UploadNSFW: false,
AssetsPath: assetsPath,
AutoIndex: -1,
AutoImport: 7200,
StoragePath: testDataPath,
CachePath: testDataPath + "/cache",
OriginalsPath: testDataPath + "/originals",
ImportPath: testDataPath + "/import",
TempPath: testDataPath + "/temp",
ConfigPath: testDataPath + "/config",
SidecarPath: testDataPath + "/sidecar",
DatabaseDriver: dbDriver,
DatabaseDsn: dbDsn,
AdminPassword: "photoprism",
Name: "PhotoPrism",
Version: "0.0.0",
Copyright: "(c) 2018-2021 Michael Mayer",
Debug: true,
Public: true,
Experimental: true,
ReadOnly: false,
DetectNSFW: true,
UploadNSFW: false,
AssetsPath: assetsPath,
AutoIndex: -1,
AutoImport: 7200,
StoragePath: testDataPath,
CachePath: testDataPath + "/cache",
OriginalsPath: testDataPath + "/originals",
ImportPath: testDataPath + "/import",
TempPath: testDataPath + "/temp",
ConfigPath: testDataPath + "/config",
SidecarPath: testDataPath + "/sidecar",
DatabaseDriver: dbDriver,
DatabaseDsn: dbDsn,
AdminPassword: "photoprism",
OidcIssuer: "http://host.docker.internal:9998",
OidcClientID: "native",
OidcClientSecret: "random",
}
return c

View file

@ -26,7 +26,18 @@ type LoggingRoundTripper struct {
func (lrt LoggingRoundTripper) RoundTrip(req *http.Request) (res *http.Response, e error) {
// Do "before sending requests" actions here.
log.Debugf("Sending request to %v\n", req.URL)
log.Debugf("Sending request to %v\n", req.URL.String())
// Copy body into buffer for logging
//bu := new(bytes.Buffer)
//_, err := io.Copy(bu, req.Body)
//if err != nil {
// log.Errorf("Error: %v", err)
//}
//log.Debugf("Request Header: %s\n", req.Header)
//log.Debugf("Request Body: %s\n", bu.String())
//req.Body = io.NopCloser(bu)
// Send the request, get the response (or the error)
res, e = lrt.proxy.RoundTrip(req)

View file

@ -1,9 +1,10 @@
package httpclient
import (
"github.com/stretchr/testify/assert"
"net/http"
"testing"
"github.com/stretchr/testify/assert"
)
func TestClient(t *testing.T) {