final fixes for external authentication

This commit is contained in:
Steffen Vogel 2021-02-04 20:04:29 +01:00
parent 914db083ed
commit b24387a22a
3 changed files with 96 additions and 80 deletions

View file

@ -62,8 +62,15 @@ func InitConfig() error {
s3PathStyle = flag.Bool("s3-pathstyle", false, "Use path-style S3 API") s3PathStyle = flag.Bool("s3-pathstyle", false, "Use path-style S3 API")
jwtSecret = flag.String("jwt-secret", "This should NOT be here!!@33$8&", "The JSON Web Token secret") 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") jwtExpiresAfter = flag.String("jwt-expires-after", "168h" /* 1 week */, "The time after which the JSON Web Token expires")
authExternal = flag.Bool("auth-external", false, "Use external authentication via X-Forwarded-User header (e.g. OAuth2 Proxy)")
authExternalAuthorizeURL = flag.String("authexternal-authorize-url", "/oauth2/start", "A URL to initiate external login procedure")
authExternalProviderName = flag.String("auth-external-provider-name", "JupyterHub", "A name of the external authentication provider")
authLogoutURL = flag.String("auth-logout-url", "/oauth2/sign_out?rd=https%3A%2F%2Fjupyter.k8s.eonerc.rwth-aachen.de%2Fhub%2Flogout", "The URL to redirect the user to log out")
title = flag.String("title", "VILLASweb", "Title shown in the frontend")
subTitle = flag.String("sub-title", "", "Sub-title shown in the frontend")
contactName = flag.String("contact-name", "Steffen Vogel", "Name of the administrative contact")
contactMail = flag.String("contact-mail", "svogel2@eonerc.rwth-aachen.de", "EMail of the administrative contact")
testDataPath = flag.String("test-data-path", "database/testdata.json", "The path to the test data json file (used in test mode)") 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() flag.Parse()
@ -88,6 +95,13 @@ func InitConfig() error {
"s3.region": *s3Region, "s3.region": *s3Region,
"jwt.secret": *jwtSecret, "jwt.secret": *jwtSecret,
"jwt.expires-after": *jwtExpiresAfter, "jwt.expires-after": *jwtExpiresAfter,
"auth.external.authorize-url": *authExternalAuthorizeURL,
"auth.external.provider-name": *authExternalProviderName,
"auth.logout-url": *authLogoutURL,
"title": *title,
"sub-title": *subTitle,
"contact.name": *contactName,
"contact.mail": *contactMail,
"test.datapath": *testDataPath, "test.datapath": *testDataPath,
} }
@ -104,16 +118,16 @@ func InitConfig() error {
} }
if *authExternal == true { if *authExternal == true {
static["auth.external"] = "true" static["auth.external.enabled"] = "true"
} else { } else {
static["auth.external"] = "false" static["auth.external.enabled"] = "false"
} }
mappings := map[string]string{} mappings := map[string]string{}
for name, value := range static { for name := range static {
envName := strings.Replace(name, ".", "_") envName := strings.ReplaceAll(name, ".", "_")
envName := strings.Replace(envName, "-", "_") envName = strings.ReplaceAll(envName, "-", "_")
envName := strings.ToUpper(envName) envName = strings.ToUpper(envName)
mappings[envName] = name mappings[envName] = name
} }

View file

@ -39,7 +39,6 @@ type ConfigAuthenticationExternal struct {
type ConfigAuthentication struct { type ConfigAuthentication struct {
External ConfigAuthenticationExternal `json:"external"` External ConfigAuthenticationExternal `json:"external"`
LoginURL string `json:"login_url"`
LogoutURL string `json:"logout_url"` LogoutURL string `json:"logout_url"`
} }
@ -76,8 +75,8 @@ func getConfig(c *gin.Context) {
resp.BasePath, _ = cfg.String("base.path") resp.BasePath, _ = cfg.String("base.path")
resp.Authentication.LogoutURL, _ = cfg.String("auth.logout-url") resp.Authentication.LogoutURL, _ = cfg.String("auth.logout-url")
resp.Authentication.External.Enabled, _ = cfg.Bool("auth.external") resp.Authentication.External.Enabled, _ = cfg.Bool("auth.external")
resp.Authentication.External.AuthorizeURL, _ = cfg.String("auth.external-authorize-url") resp.Authentication.External.AuthorizeURL, _ = cfg.String("auth.external.authorize-url")
resp.Authentication.External.ProviderName, _ = cfg.String("auth.external-provider-name") resp.Authentication.External.ProviderName, _ = cfg.String("auth.external.provider-name")
resp.Title, _ = cfg.String("title") resp.Title, _ = cfg.String("title")
resp.SubTitle, _ = cfg.String("sub-title") resp.SubTitle, _ = cfg.String("sub-title")
resp.Contact.Name, _ = cfg.String("contact.name") resp.Contact.Name, _ = cfg.String("contact.name")

View file

@ -41,7 +41,7 @@ type tokenClaims struct {
func RegisterAuthenticate(r *gin.RouterGroup) { func RegisterAuthenticate(r *gin.RouterGroup) {
r.GET("", authenticated) r.GET("", authenticated)
r.POST("", authenticate) r.POST("/:mechanism", authenticate)
} }
// authenticated godoc // authenticated godoc
@ -105,48 +105,57 @@ func authenticated(c *gin.Context) {
// @Produce json // @Produce json
// @Tags authentication // @Tags authentication
// @Param inputUser body user.loginRequest true "loginRequest of user" // @Param inputUser body user.loginRequest true "loginRequest of user"
// @Param mechanism path string true "Login mechanism" Enums(internal, external)
// @Success 200 {object} api.ResponseAuthenticate "JSON web token, success status, message and authenticated user object" // @Success 200 {object} api.ResponseAuthenticate "JSON web token, success status, message and authenticated user object"
// @Failure 401 {object} api.ResponseError "Unauthorized" // @Failure 401 {object} api.ResponseError "Unauthorized"
// @Failure 500 {object} api.ResponseError "Internal server error." // @Failure 500 {object} api.ResponseError "Internal server error."
// @Router /authenticate [post] // @Router /authenticate{mechanism} [post]
func authenticate(c *gin.Context) { func authenticate(c *gin.Context) {
var user *User var user *User = nil
switch c.Param("mechanism") {
case "internal":
user = authenticateInternal(c)
case "external":
authExternal, err := configuration.GlobalConfig.Bool("auth.external") authExternal, err := configuration.GlobalConfig.Bool("auth.external")
if err != nil { if err == nil && authExternal {
helper.UnauthorizedError(c, "Backend configuration error")
return
}
if err != nil || !authExternal {
user = authenticateStandard(c)
} else {
user = authenticateExternal(c) user = authenticateExternal(c)
} else {
helper.BadRequestError(c, "External authentication is not activated")
}
default:
helper.BadRequestError(c, "Invalid authentication mechanism")
} }
if user == nil { if user == nil {
return return
} }
// Check if this is an active user
if !user.Active {
helper.UnauthorizedError(c, "User is not active")
return
}
expiresStr, err := configuration.GlobalConfig.String("jwt.expires-after") expiresStr, err := configuration.GlobalConfig.String("jwt.expires-after")
if err != nil { if err != nil {
helper.UnauthorizedError(c, "Backend configuration error") helper.InternalServerError(c, "Invalid backend configuration: jwt.expires-after")
return return
} }
expiresDuration, err := time.ParseDuration(expiresStr) expiresDuration, err := time.ParseDuration(expiresStr)
if err != nil { if err != nil {
helper.UnauthorizedError(c, "Backend configuration error") helper.InternalServerError(c, "Invalid backend configuration: jwt.expires-after")
return return
} }
secret, err := configuration.GlobalConfig.String("jwt.secret") secret, err := configuration.GlobalConfig.String("jwt.secret")
if err != nil { if err != nil {
helper.UnauthorizedError(c, "Backend configuration error") helper.InternalServerError(c, "Invalid backend configuration: jwt.secret")
return return
} }
// create authentication token // Create authentication token
claims := tokenClaims{ claims := tokenClaims{
user.ID, user.ID,
user.Role, user.Role,
@ -161,7 +170,7 @@ func authenticate(c *gin.Context) {
tokenString, err := token.SignedString([]byte(secret)) tokenString, err := token.SignedString([]byte(secret))
if err != nil { if err != nil {
helper.InternalServerError(c, err.Error()) helper.InternalServerError(c, "Invalid backend configuration: jwt.secret")
return return
} }
@ -173,7 +182,7 @@ func authenticate(c *gin.Context) {
}) })
} }
func authenticateStandard(c *gin.Context) *User { func authenticateInternal(c *gin.Context) *User {
// Bind the response (context) with the loginRequest struct // Bind the response (context) with the loginRequest struct
var credentials loginRequest var credentials loginRequest
if err := c.ShouldBindJSON(&credentials); err != nil { if err := c.ShouldBindJSON(&credentials); err != nil {
@ -195,12 +204,6 @@ func authenticateStandard(c *gin.Context) *User {
return nil return nil
} }
// Check if this is an active user
if !user.Active {
helper.UnauthorizedError(c, "User is not active")
return nil
}
// Validate the password // Validate the password
err = user.validatePassword(credentials.Password) err = user.validatePassword(credentials.Password)
if err != nil { if err != nil {