Auth: Add Tests #782
This commit is contained in:
parent
dea527591e
commit
9bdc38be9a
6 changed files with 161 additions and 36 deletions
|
@ -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)
|
||||
|
|
|
@ -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
93
internal/api/auth_test.go
Normal 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")
|
||||
})
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Add table
Reference in a new issue