From fe2d291bb3eb5c4a2d835c9af33e94c5a86a993b Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Mon, 25 Jan 2021 22:34:30 +0100 Subject: [PATCH] add support for external authentication via OAuth2 proxy --- configuration/config.go | 8 ++ helper/utilities.go | 14 +++- routes/user/authenticate_endpoint.go | 110 ++++++++++++++++++++------- routes/user/user_endpoints.go | 2 - 4 files changed, 103 insertions(+), 31 deletions(-) diff --git a/configuration/config.go b/configuration/config.go index f44c044..48b7794 100644 --- a/configuration/config.go +++ b/configuration/config.go @@ -62,6 +62,7 @@ func InitConfig() error { jwtSecret = flag.String("jwt-secret", "This should NOT be here!!@33$8&", "The JSON Web Token secret") jwtExpiresAfter = flag.String("jwt-expires-after", "168h" /* 1 week */, "The time after which the JSON Web Token expires") testDataPath = flag.String("test-data-path", "database/testdata.json", "The path to the test data json file (used in test mode)") + authExternal = flag.Bool("auth.external", false, "Use external authentication via X-Forwarded-User header (e.g. OAuth2 Proxy)") ) flag.Parse() @@ -101,6 +102,12 @@ func InitConfig() error { static["s3.pathstyle"] = "false" } + if *authExternal == true { + static["auth.external"] = "true" + } else { + static["auth.external"] = "false" + } + mappings := map[string]string{ "DB_HOST": "db.host", "DB_NAME": "db.name", @@ -125,6 +132,7 @@ func InitConfig() error { "JWT_SECRET": "jwt.secret", "JWT_EXPIRES_AFTER": "jwt.expires-after", "TEST_DATA_PATH": "test.datapath", + "AUTH_EXTERNAL": "auth.external", } defaults := config.NewStatic(static) diff --git a/helper/utilities.go b/helper/utilities.go index 153b5e8..1a2f795 100644 --- a/helper/utilities.go +++ b/helper/utilities.go @@ -23,8 +23,9 @@ package helper import ( "fmt" - "github.com/gin-gonic/gin" "strconv" + + "github.com/gin-gonic/gin" ) func GetIDOfElement(c *gin.Context, elementName string, source string, providedID int) (int, error) { @@ -50,3 +51,14 @@ func GetIDOfElement(c *gin.Context, elementName string, source string, providedI return -1, fmt.Errorf("invalid source of element ID") } } + +// Find takes a slice and looks for an element in it. If found it will +// return it's key, otherwise it will return -1 and a bool of false. +func Find(slice []string, val string) (int, bool) { + for i, item := range slice { + if item == val { + return i, true + } + } + return -1, false +} diff --git a/routes/user/authenticate_endpoint.go b/routes/user/authenticate_endpoint.go index 9fee084..687fb37 100644 --- a/routes/user/authenticate_endpoint.go +++ b/routes/user/authenticate_endpoint.go @@ -23,6 +23,7 @@ package user import ( "net/http" + "strings" "time" "git.rwth-aachen.de/acs/public/villas/web-backend-go/configuration" @@ -53,39 +54,18 @@ func RegisterAuthenticate(r *gin.RouterGroup) { // @Failure 500 {object} api.ResponseError "Internal server error." // @Router /authenticate [post] func authenticate(c *gin.Context) { + var user *User - // Bind the response (context) with the loginRequest struct - var credentials loginRequest - if err := c.ShouldBindJSON(&credentials); err != nil { - helper.UnauthorizedError(c, "Wrong username or password") - return - } - - // Validate the login request - if errs := credentials.validate(); errs != nil { - helper.UnauthorizedError(c, "Wrong username or password") - return - } - - // Find the username in the database - var user User - err := user.ByUsername(credentials.Username) + externalAuth, err := configuration.GlobalConfig.Bool("external-auth") if err != nil { - helper.UnauthorizedError(c, "Wrong username or password") + helper.UnauthorizedError(c, "Backend configuration error") return } - // Check if this is an active user - if !user.Active { - helper.UnauthorizedError(c, "Wrong username or password") - return - } - - // Validate the password - err = user.validatePassword(credentials.Password) - if err != nil { - helper.UnauthorizedError(c, "Wrong username or password") - return + if err != nil || !externalAuth { + user = authenticateStandard(c) + } else { + user = authenticateExternal(c) } expiresStr, err := configuration.GlobalConfig.String("jwt.expires-after") @@ -132,3 +112,77 @@ func authenticate(c *gin.Context) { "user": user.User, }) } + +func authenticateStandard(c *gin.Context) *User { + // Bind the response (context) with the loginRequest struct + var credentials loginRequest + if err := c.ShouldBindJSON(&credentials); err != nil { + helper.UnauthorizedError(c, "Wrong username or password") + return nil + } + + // Validate the login request + if errs := credentials.validate(); errs != nil { + helper.UnauthorizedError(c, "Failed to validate request") + return nil + } + + // Find the username in the database + var user User + err := user.ByUsername(credentials.Username) + if err != nil { + helper.UnauthorizedError(c, "Unknown username") + return nil + } + + // Check if this is an active user + if !user.Active { + helper.UnauthorizedError(c, "User is not active") + return nil + } + + // Validate the password + err = user.validatePassword(credentials.Password) + if err != nil { + helper.UnauthorizedError(c, "Invalid password") + return nil + } + + return &user +} + +func authenticateExternal(c *gin.Context) *User { + username := c.Request.Header.Get("X-Forwarded-User") + if username == "" { + helper.UnauthorizedAbort(c, "Authentication failed (X-Forwarded-User headers)") + return nil + } + + email := c.Request.Header.Get("X-Forwarded-Email") + if email == "" { + helper.UnauthorizedAbort(c, "Authentication failed (X-Forwarded-Email headers)") + return nil + } + + groups := strings.Split(c.Request.Header.Get("X-Forwarded-Groups"), ",") + // preferred_username := c.Request.Header.Get("X-Forwarded-Preferred-Username") + + var user User + if err := user.ByUsername(username); err == nil { + // There is already a user by this name + return &user + } else { + role := "User" + if _, found := helper.Find(groups, "admin"); found { + role = "Admin" + } + + newUser, err := NewUser(username, "", email, role, true) + if err != nil { + helper.UnauthorizedAbort(c, "Authentication failed (failed to create new user: "+err.Error()+")") + return nil + } + + return newUser + } +} diff --git a/routes/user/user_endpoints.go b/routes/user/user_endpoints.go index 5e04b76..95b0a9d 100644 --- a/routes/user/user_endpoints.go +++ b/routes/user/user_endpoints.go @@ -28,8 +28,6 @@ import ( "git.rwth-aachen.de/acs/public/villas/web-backend-go/helper" - "git.rwth-aachen.de/acs/public/villas/web-backend-go/helper" - "github.com/gin-gonic/gin" "git.rwth-aachen.de/acs/public/villas/web-backend-go/database"