add support for external authentication via OAuth2 proxy

This commit is contained in:
Steffen Vogel 2021-01-25 22:34:30 +01:00
parent ffa532bddc
commit fe2d291bb3
4 changed files with 103 additions and 31 deletions

View file

@ -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)

View file

@ -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
}

View file

@ -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
}
}

View file

@ -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"