Skip to content

Commit

Permalink
Merge pull request #167 from croessner/features
Browse files Browse the repository at this point in the history
Features
  • Loading branch information
croessner authored Nov 21, 2024
2 parents f9cdccd + 1f08b09 commit a16deca
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 105 deletions.
72 changes: 37 additions & 35 deletions server/core/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ type JSONRequest struct {
Username string `json:"username" binding:"required"`

// Password is the authentication credential of the client/user sending the request.
Password string `json:"password" binding:"required"`
Password string `json:"password,omitempty"`

// ClientIP is the IP address of the client/user making the request.
ClientIP string `json:"client_ip,omitempty"`
Expand Down Expand Up @@ -251,7 +251,7 @@ type AuthState struct {
// StatusMessage is the HTTP response payload that is sent to the remote server that asked for authentication.
StatusMessage string

// Service is set by Nauthilus depending on the router endpoint. Look at httpQueryHandler for the structure of available
// Service is set by Nauthilus depending on the router endpoint. Look at requestHandler for the structure of available
// endpoints.
Service string

Expand Down Expand Up @@ -580,9 +580,9 @@ func (a *AuthState) authOK(ctx *gin.Context) {
switch a.Service {
case global.ServNginx:
setNginxHeaders(ctx, a)
case global.ServDovecot:
setDovecotHeaders(ctx, a)
case global.ServUserInfo, global.ServJSON:
case global.ServHeader:
setHeaderHeaders(ctx, a)
case global.ServJSON:
sendAuthResponse(ctx, a)
}

Expand All @@ -597,13 +597,13 @@ func (a *AuthState) authOK(ctx *gin.Context) {

// setCommonHeaders sets common headers for the given gin.Context and AuthState.
// It sets the "Auth-Status" header to "OK" and the "X-Nauthilus-Session" header to the GUID of the AuthState.
// If the AuthState's Service is not global.ServBasicAuth, and the HaveAccountField flag is true,
// If the AuthState's Service is not global.ServBasic, and the HaveAccountField flag is true,
// it retrieves the account from the AuthState and sets the "Auth-User" header
func setCommonHeaders(ctx *gin.Context, a *AuthState) {
ctx.Header("Auth-Status", "OK")
ctx.Header("X-Nauthilus-Session", *a.GUID)

if a.Service != global.ServBasicAuth {
if a.Service != global.ServBasic {
if account, found := a.getAccountOk(); found {
ctx.Header("Auth-User", account)
}
Expand Down Expand Up @@ -650,7 +650,7 @@ func setNginxHeaders(ctx *gin.Context, a *AuthState) {
}
}

// setDovecotHeaders sets the specified headers in the given gin.Context based on the attributes in the AuthState object.
// setHeaderHeaders sets the specified headers in the given gin.Context based on the attributes in the AuthState object.
// It iterates through the attributes and calls the handleAttributeValue function for each attribute.
//
// Parameters:
Expand All @@ -665,12 +665,12 @@ func setNginxHeaders(ctx *gin.Context, a *AuthState) {
// "Attribute2": []any{"Value2_1", "Value2_2"},
// },
// }
// setDovecotHeaders(ctx, a)
// setHeaderHeaders(ctx, a)
//
// Resulting headers in ctx:
// - X-Nauthilus-Attribute1: "Value1"
// - X-Nauthilus-Attribute2: "Value2_1,Value2_2"
func setDovecotHeaders(ctx *gin.Context, a *AuthState) {
func setHeaderHeaders(ctx *gin.Context, a *AuthState) {
if a.Attributes != nil && len(a.Attributes) > 0 {
for name, value := range a.Attributes {
handleAttributeValue(ctx, name, value)
Expand Down Expand Up @@ -805,7 +805,7 @@ func (a *AuthState) setFailureHeaders(ctx *gin.Context) {

ctx.Header("Auth-Wait", fmt.Sprintf("%v", waitDelay))
}
case global.ServUserInfo, global.ServJSON:
case global.ServJSON:
ctx.Header("Content-Type", "application/json; charset=UTF-8")

if a.PasswordHistory != nil {
Expand Down Expand Up @@ -859,7 +859,7 @@ func (a *AuthState) setSMPTHeaders(ctx *gin.Context) {
//
// func (a *AuthState) authTempFail(ctx *gin.Context, reason string) {
// ...
// if a.Service == global.ServUserInfo {
// if a.Service == global.ServJSON {
// a.sendAuthResponse(ctx, reason)
// return
// }
Expand All @@ -875,7 +875,6 @@ func (a *AuthState) setUserInfoHeaders(ctx *gin.Context, reason string) {
}

ctx.Header("Content-Type", "application/json; charset=UTF-8")
ctx.Header("X-User-Found", fmt.Sprintf("%v", a.UserFound))

ctx.JSON(a.StatusCodeInternalError, &errType{Error: reason})
}
Expand All @@ -892,12 +891,12 @@ func (a *AuthState) setUserInfoHeaders(ctx *gin.Context, reason string) {
//
// Usage example:
//
// func (a *AuthState) generic(ctx *gin.Context) {
// func (a *AuthState) handleAuthentication(ctx *gin.Context) {
// ...
// a.authTempFail(ctx, global.TempFailDefault)
// ...
// }
// func (a *AuthState) saslAuthd(ctx *gin.Context) {
// func (a *AuthState) handleSASLAuthdAuthentication(ctx *gin.Context) {
// ...
// a.authTempFail(ctx, global.TempFailDefault)
// ...
Expand All @@ -915,8 +914,9 @@ func (a *AuthState) authTempFail(ctx *gin.Context, reason string) {

a.StatusMessage = reason

if a.Service == global.ServUserInfo {
if a.Service == global.ServJSON {
a.setUserInfoHeaders(ctx, reason)

return
}

Expand Down Expand Up @@ -1117,11 +1117,11 @@ func updateAuthentication(a *AuthState, passDBResult *PassDBResult, passDB *Pass
// setStatusCodes sets different status codes for various services.
func (a *AuthState) setStatusCodes(service string) error {
switch service {
case global.ServNginx, global.ServDovecot:
case global.ServNginx:
a.StatusCodeOK = http.StatusOK
a.StatusCodeInternalError = http.StatusOK
a.StatusCodeFail = http.StatusOK
case global.ServSaslauthd, global.ServBasicAuth, global.ServOryHydra, global.ServUserInfo, global.ServJSON, global.ServCallback:
case global.ServSaslauthd, global.ServBasic, global.ServOryHydra, global.ServHeader, global.ServJSON, global.ServCallback:
a.StatusCodeOK = http.StatusOK
a.StatusCodeInternalError = http.StatusInternalServerError
a.StatusCodeFail = http.StatusForbidden
Expand Down Expand Up @@ -2149,22 +2149,24 @@ func setAuthenticationFields(auth *AuthState, request *JSONRequest) {
// If neither of the above conditions match, it sets the error associated with unsupported media type
// and sets the error type to gin.ErrorTypeBind on the Context.
func setupBodyBasedAuth(ctx *gin.Context, auth *AuthState) {
contentType := ctx.GetHeader("Content-Type")
if ctx.Request.Method == "POST" {
contentType := ctx.GetHeader("Content-Type")

if strings.HasPrefix(contentType, "application/x-www-form-urlencoded") {
processApplicationXWWWFormUrlencoded(ctx, auth)
} else if contentType == "application/json" {
processApplicationJSON(ctx, auth)
} else {
ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Unsupported media type"})
ctx.Error(errors.ErrUnsupportedMediaType).SetType(gin.ErrorTypeBind)
if strings.HasPrefix(contentType, "application/x-www-form-urlencoded") {
processApplicationXWWWFormUrlencoded(ctx, auth)
} else if contentType == "application/json" {
processApplicationJSON(ctx, auth)
} else {
ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Unsupported media type"})
ctx.Error(errors.ErrUnsupportedMediaType).SetType(gin.ErrorTypeBind)
}
}
}

// setupHTTPBasiAuth sets up basic authentication for HTTP requests.
// setupHTTPBasicAuth sets up basic authentication for HTTP requests.
// It takes in a gin.Context object and a pointer to an AuthState object.
// It calls the withClientInfo, withLocalInfo, withUserAgent, and withXSSL methods of the AuthState object to set client, local, user-agent, and X-SSL information, respectively
func setupHTTPBasiAuth(ctx *gin.Context, auth *AuthState) {
func setupHTTPBasicAuth(ctx *gin.Context, auth *AuthState) {
// NOTE: We must get username and password later!
auth.withClientInfo(ctx)
auth.withLocalInfo(ctx)
Expand All @@ -2190,9 +2192,9 @@ func (a *AuthState) initMethodAndUserAgent() *AuthState {
// setupAuth sets up the authentication based on the service parameter in the gin context.
// It takes the gin context and an AuthState struct as input.
//
// If the service parameter is "nginx", "dovecot", or "user", it calls the setupHeaderBasedAuth function.
// If the service parameter is "nginx" or "header", it calls the setupHeaderBasedAuth function.
// If the service parameter is "saslauthd", it calls the setupBodyBasedAuth function.
// If the service parameter is "basicauth", it calls the setupHTTPBasiAuth function.
// If the service parameter is "basicauth", it calls the setupHTTPBasicAuth function.
//
// After setting up the authentication, it calls the withDefaults method on the AuthState struct.
//
Expand All @@ -2206,19 +2208,19 @@ func setupAuth(ctx *gin.Context, auth *AuthState) {
auth.Protocol = &config.Protocol{}

switch ctx.Param("service") {
case global.ServNginx, global.ServDovecot, global.ServUserInfo:
case global.ServNginx, global.ServHeader:
setupHeaderBasedAuth(ctx, auth)
case global.ServSaslauthd, global.ServJSON:
setupBodyBasedAuth(ctx, auth)
case global.ServBasicAuth:
setupHTTPBasiAuth(ctx, auth)
case global.ServBasic:
setupHTTPBasicAuth(ctx, auth)
case global.ServCallback:
auth.withDefaults(ctx)

return
}

if ctx.Query("mode") != "list-accounts" && ctx.Param("service") != global.ServBasicAuth {
if ctx.Query("mode") != "list-accounts" && ctx.Param("service") != global.ServBasic {
if !util.ValidateUsername(auth.Username) {
auth.Username = ""

Expand Down Expand Up @@ -2291,7 +2293,7 @@ func (a *AuthState) withDefaults(ctx *gin.Context) *AuthState {
a.Service = ctx.Param("service")
a.Context = ctx.MustGet(global.CtxDataExchangeKey).(*lualib.Context)

if a.Service == global.ServBasicAuth {
if a.Service == global.ServBasic {
a.Protocol.Set(global.ProtoHTTP)
}

Expand Down
2 changes: 1 addition & 1 deletion server/core/features.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func isLocalOrEmptyIP(ip string) bool {
return ip == global.Localhost4 || ip == global.Localhost6 || ip == ""
}

// logAddMessage logs a message with the specified parameters using the global logger. It is intended to be a generic logging function.
// logAddMessage logs a message with the specified parameters using the global logger. It is intended to be a handleAuthentication logging function.
//
// Parameters:
// - auth: Pointer to AuthState
Expand Down
57 changes: 18 additions & 39 deletions server/core/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ var (
LangBundle *i18n.Bundle
)

// RESTResult is a generic JSON result object for the Nauthilus REST API.
// RESTResult is a handleAuthentication JSON result object for the Nauthilus REST API.
type RESTResult struct {
// GUID represents a unique identifier for a session. It is a string field used in the RESTResult struct
// and is also annotated with the json tag "session".
Expand Down Expand Up @@ -165,12 +165,12 @@ func (w *customWriter) Write(data []byte) (numBytes int, err error) {
}

//nolint:gocognit // Main logic
func httpQueryHandler(ctx *gin.Context) {
func requestHandler(ctx *gin.Context) {
if ctx.FullPath() == "/ping" {
healthCheck(ctx)
} else {
switch ctx.Param("category") {
case global.CatMail, global.CatGeneric:
case global.CatAuth:
auth := NewAuthState(ctx)
if auth == nil {
ctx.AbortWithStatus(http.StatusBadRequest)
Expand All @@ -185,42 +185,21 @@ func httpQueryHandler(ctx *gin.Context) {
}

switch ctx.Param("service") {
case global.ServNginx, global.ServDovecot, global.ServUserInfo, global.ServJSON:
auth.generic(ctx)
case global.ServBasic, global.ServNginx, global.ServHeader, global.ServJSON:
auth.handleAuthentication(ctx)
case global.ServSaslauthd:
auth.saslAuthd(ctx)
auth.handleSASLAuthdAuthentication(ctx)
case global.ServCallback:
auth.callback(ctx)
auth.handleCallback(ctx)
ctx.Status(auth.StatusCodeOK)
default:
ctx.AbortWithStatus(http.StatusNotFound)
}

case global.CatHTTP:
auth := NewAuthState(ctx)
if auth == nil {
ctx.AbortWithStatus(http.StatusBadRequest)

return
}

if found, reject := auth.preproccessAuthRequest(ctx); reject {
return
} else if found {
auth.withClientInfo(ctx).withLocalInfo(ctx).withUserAgent(ctx).withXSSL(ctx)
}

switch ctx.Param("service") {
case global.ServBasicAuth:
auth.generic(ctx)
default:
ctx.AbortWithStatus(http.StatusNotFound)
}

case global.CatBruteForce:
switch ctx.Param("service") {
case global.ServList:
listBruteforce(ctx)
hanldeBruteForceList(ctx)
default:
ctx.AbortWithStatus(http.StatusNotFound)
}
Expand All @@ -239,23 +218,23 @@ func httpQueryHandler(ctx *gin.Context) {
// 2. It uses a switch statement to handle different category values.
// 3. For the "cache" category, it retrieves the "service" parameter and uses a switch statement
// to handle different service values.
// 4. For the "flush" service, it calls the flushCache function.
// 4. For the "flush" service, it calls the handleUserFlush function.
// 5. For the "bruteforce" category, it retrieves the "service" parameter and uses a switch statement
// to handle different service values.
// 6. For the "flush" service, it calls the flushBruteForceRule function.
// 6. For the "flush" service, it calls the handleBruteForceRuleFlush function.
func httpCacheHandler(ctx *gin.Context) {
//nolint:gocritic // Prepared for future commands
switch ctx.Param("category") {
case global.CatCache:
switch ctx.Param("service") {
case global.ServFlush:
flushCache(ctx)
handleUserFlush(ctx)
}

case global.CatBruteForce:
switch ctx.Param("service") {
case global.ServFlush:
flushBruteForceRule(ctx)
handleBruteForceRuleFlush(ctx)
}
}
}
Expand Down Expand Up @@ -359,7 +338,7 @@ func basicAuthMiddleware() gin.HandlerFunc {
guid := ctx.GetString(global.CtxGUIDKey)

// Note: Chicken-egg problem.
if ctx.Param("category") == global.CatHTTP && ctx.Param("service") == global.ServBasicAuth {
if ctx.Param("category") == global.CatAuth && ctx.Param("service") == global.ServBasic {
level.Warn(log.Logger).Log(
global.LogKeyGUID, guid,
global.LogKeyMsg, "Disabling HTTP basic Auth",
Expand Down Expand Up @@ -732,8 +711,8 @@ func setupNotifyEndpoint(router *gin.Engine, sessionStore sessions.Store) {
// it adds a middleware to the group that implements basic authentication.
//
// It then adds three endpoints to the group:
// - A GET endpoint with the path "/:category/:service" that is handled by the luaContextMiddleware and httpQueryHandler functions.
// - A POST endpoint with the path "/:category/:service" that is also handled by the luaContextMiddleware and httpQueryHandler functions.
// - A GET endpoint with the path "/:category/:service" that is handled by the luaContextMiddleware and requestHandler functions.
// - A POST endpoint with the path "/:category/:service" that is also handled by the luaContextMiddleware and requestHandler functions.
// - A DELETE endpoint with the path "/:category/:service" that is handled by the httpCacheHandler function.
func setupBackChannelEndpoints(router *gin.Engine) {
group := router.Group("/api/v1")
Expand All @@ -742,8 +721,8 @@ func setupBackChannelEndpoints(router *gin.Engine) {
group.Use(basicAuthMiddleware())
}

group.GET("/:category/:service", luaContextMiddleware(), httpQueryHandler)
group.POST("/:category/:service", luaContextMiddleware(), httpQueryHandler)
group.GET("/:category/:service", luaContextMiddleware(), requestHandler)
group.POST("/:category/:service", luaContextMiddleware(), requestHandler)
group.DELETE("/:category/:service", httpCacheHandler)
}

Expand Down Expand Up @@ -990,7 +969,7 @@ func setupRouter(router *gin.Engine) {
router.GET("/metrics", gin.WrapF(promhttp.Handler().ServeHTTP))

// Healthcheck
router.GET("/ping", httpQueryHandler)
router.GET("/ping", requestHandler)

// Parse static folder for template files
router.LoadHTMLGlob(viper.GetString("html_static_content_path") + "/*.html")
Expand Down
Loading

0 comments on commit a16deca

Please sign in to comment.