cc-backend/internal/auth/auth.go

262 lines
7.6 KiB
Go
Raw Normal View History

// Copyright (C) 2023 NHR@FAU, University Erlangen-Nuremberg.
// All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package auth
import (
"context"
2022-07-25 09:33:36 +02:00
"crypto/rand"
"database/sql"
2022-07-25 09:33:36 +02:00
"encoding/base64"
"errors"
"net/http"
2022-07-25 09:33:36 +02:00
"os"
"time"
"github.com/ClusterCockpit/cc-backend/internal/repository"
2022-06-21 17:52:36 +02:00
"github.com/ClusterCockpit/cc-backend/pkg/log"
"github.com/ClusterCockpit/cc-backend/pkg/schema"
"github.com/gorilla/sessions"
)
2022-07-07 14:08:37 +02:00
type Authenticator interface {
2023-08-17 12:34:30 +02:00
Init(config interface{}) error
2023-08-17 14:02:04 +02:00
CanLogin(user *schema.User, username string, rw http.ResponseWriter, r *http.Request) (*schema.User, bool)
Login(user *schema.User, rw http.ResponseWriter, r *http.Request) (*schema.User, error)
2022-07-07 14:08:37 +02:00
}
2022-02-14 14:22:44 +01:00
type Authentication struct {
sessionStore *sessions.CookieStore
SessionMaxAge time.Duration
2022-07-07 14:08:37 +02:00
authenticators []Authenticator
LdapAuth *LdapAuthenticator
2022-07-07 14:08:37 +02:00
JwtAuth *JWTAuthenticator
LocalAuth *LocalAuthenticator
2022-02-14 14:22:44 +01:00
}
func (auth *Authentication) AuthViaSession(
rw http.ResponseWriter,
r *http.Request) (*schema.User, error) {
session, err := auth.sessionStore.Get(r, "session")
if err != nil {
log.Error("Error while getting session store")
return nil, err
}
if session.IsNew {
return nil, nil
}
//
// var username string
// var projects, roles []string
//
// if val, ok := session.Values["username"]; ok {
// username, _ = val.(string)
// } else {
// return nil, errors.New("no key username in session")
// }
// if val, ok := session.Values["projects"]; ok {
// projects, _ = val.([]string)
// } else {
// return nil, errors.New("no key projects in session")
// }
// if val, ok := session.Values["projects"]; ok {
// roles, _ = val.([]string)
// } else {
// return nil, errors.New("no key roles in session")
// }
//
username, _ := session.Values["username"].(string)
projects, _ := session.Values["projects"].([]string)
roles, _ := session.Values["roles"].([]string)
return &schema.User{
Username: username,
Projects: projects,
Roles: roles,
AuthType: schema.AuthSession,
AuthSource: -1,
}, nil
}
2023-08-17 12:34:30 +02:00
func Init(configs map[string]interface{}) (*Authentication, error) {
2022-07-07 14:08:37 +02:00
auth := &Authentication{}
2022-03-03 14:54:37 +01:00
2022-07-25 09:33:36 +02:00
sessKey := os.Getenv("SESSION_KEY")
if sessKey == "" {
log.Warn("environment variable 'SESSION_KEY' not set (will use non-persistent random key)")
bytes := make([]byte, 32)
if _, err := rand.Read(bytes); err != nil {
log.Error("Error while initializing authentication -> failed to generate random bytes for session key")
2022-07-25 09:33:36 +02:00
return nil, err
}
auth.sessionStore = sessions.NewCookieStore(bytes)
} else {
bytes, err := base64.StdEncoding.DecodeString(sessKey)
if err != nil {
log.Error("Error while initializing authentication -> decoding session key failed")
2022-07-25 09:33:36 +02:00
return nil, err
}
auth.sessionStore = sessions.NewCookieStore(bytes)
}
2022-07-07 14:08:37 +02:00
auth.JwtAuth = &JWTAuthenticator{}
2023-08-18 08:49:25 +02:00
if err := auth.JwtAuth.Init(); err != nil {
log.Error("Error while initializing authentication -> jwtAuth init failed")
2022-07-07 14:08:37 +02:00
return nil, err
}
2022-07-07 14:08:37 +02:00
if config, ok := configs["ldap"]; ok {
2023-08-16 09:19:41 +02:00
ldapAuth := &LdapAuthenticator{}
2023-08-17 12:34:30 +02:00
if err := ldapAuth.Init(config); err != nil {
2023-08-16 09:19:41 +02:00
log.Warn("Error while initializing authentication -> ldapAuth init failed")
} else {
auth.LdapAuth = ldapAuth
auth.authenticators = append(auth.authenticators, auth.LdapAuth)
2022-02-14 14:22:44 +01:00
}
} else {
2023-08-18 08:49:25 +02:00
log.Info("Missing LDAP configuration: No LDAP support!")
}
2023-08-18 08:49:25 +02:00
if config, ok := configs["jwt"]; ok {
jwtSessionAuth := &JWTSessionAuthenticator{}
if err := jwtSessionAuth.Init(config); err != nil {
log.Warn("Error while initializing authentication -> jwtSessionAuth init failed")
} else {
auth.authenticators = append(auth.authenticators, jwtSessionAuth)
}
jwtCookieSessionAuth := &JWTCookieSessionAuthenticator{}
if err := jwtCookieSessionAuth.Init(configs["jwt"]); err != nil {
log.Warn("Error while initializing authentication -> jwtCookieSessionAuth init failed")
} else {
auth.authenticators = append(auth.authenticators, jwtCookieSessionAuth)
}
} else {
2023-08-18 08:49:25 +02:00
log.Info("Missing JWT configuration: No JWT token login support!")
2022-03-15 11:04:54 +01:00
}
auth.LocalAuth = &LocalAuthenticator{}
2023-08-17 12:34:30 +02:00
if err := auth.LocalAuth.Init(nil); err != nil {
log.Error("Error while initializing authentication -> localAuth init failed")
return nil, err
2022-03-15 11:04:54 +01:00
}
auth.authenticators = append(auth.authenticators, auth.LocalAuth)
2022-03-17 10:54:17 +01:00
return auth, nil
2022-03-15 11:04:54 +01:00
}
func (auth *Authentication) Login(
onsuccess http.Handler,
onfailure func(rw http.ResponseWriter, r *http.Request, loginErr error)) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
err := errors.New("no authenticator applied")
2022-07-07 14:08:37 +02:00
username := r.FormValue("username")
2023-08-17 14:02:04 +02:00
var dbUser *schema.User
2022-07-07 14:08:37 +02:00
if username != "" {
2023-08-17 12:34:30 +02:00
dbUser, err = repository.GetUserRepository().GetUser(username)
if err != nil && err != sql.ErrNoRows {
log.Errorf("Error while loading user '%v'", username)
}
}
2022-07-07 14:08:37 +02:00
for _, authenticator := range auth.authenticators {
2023-08-17 14:02:04 +02:00
var ok bool
var user *schema.User
if user, ok = authenticator.CanLogin(dbUser, username, rw, r); !ok {
2022-07-07 14:08:37 +02:00
continue
}
2023-08-17 14:02:04 +02:00
user, err = authenticator.Login(user, rw, r)
2022-07-07 14:08:37 +02:00
if err != nil {
log.Warnf("user login failed: %s", err.Error())
2022-07-07 14:08:37 +02:00
onfailure(rw, r, err)
return
}
2022-07-07 14:08:37 +02:00
session, err := auth.sessionStore.New(r, "session")
if err != nil {
log.Errorf("session creation failed: %s", err.Error())
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
2022-07-07 14:08:37 +02:00
if auth.SessionMaxAge != 0 {
session.Options.MaxAge = int(auth.SessionMaxAge.Seconds())
}
session.Values["username"] = user.Username
session.Values["projects"] = user.Projects
2022-07-07 14:08:37 +02:00
session.Values["roles"] = user.Roles
if err := auth.sessionStore.Save(r, rw, session); err != nil {
log.Warnf("session save failed: %s", err.Error())
2022-07-07 14:08:37 +02:00
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
log.Infof("login successfull: user: %#v (roles: %v, projects: %v)", user.Username, user.Roles, user.Projects)
ctx := context.WithValue(r.Context(), repository.ContextUserKey, user)
2022-07-07 14:08:37 +02:00
onsuccess.ServeHTTP(rw, r.WithContext(ctx))
2022-07-25 09:33:36 +02:00
return
}
2023-06-20 15:47:38 +02:00
log.Debugf("login failed: no authenticator applied")
2022-07-07 14:08:37 +02:00
onfailure(rw, r, err)
})
}
func (auth *Authentication) Auth(
onsuccess http.Handler,
onfailure func(rw http.ResponseWriter, r *http.Request, authErr error)) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
user, err := auth.JwtAuth.AuthViaJWT(rw, r)
if err != nil {
log.Infof("authentication failed: %s", err.Error())
http.Error(rw, err.Error(), http.StatusUnauthorized)
return
}
if user == nil {
user, err = auth.AuthViaSession(rw, r)
2022-07-07 14:08:37 +02:00
if err != nil {
2023-06-20 15:47:38 +02:00
log.Infof("authentication failed: %s", err.Error())
2022-07-07 14:08:37 +02:00
http.Error(rw, err.Error(), http.StatusUnauthorized)
return
}
}
2022-07-07 14:08:37 +02:00
if user != nil {
ctx := context.WithValue(r.Context(), repository.ContextUserKey, user)
2022-02-14 14:22:44 +01:00
onsuccess.ServeHTTP(rw, r.WithContext(ctx))
2022-07-25 09:33:36 +02:00
return
}
log.Debug("authentication failed")
onfailure(rw, r, errors.New("unauthorized (please login first)"))
})
}
2022-02-14 14:22:44 +01:00
func (auth *Authentication) Logout(onsuccess http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
session, err := auth.sessionStore.Get(r, "session")
if err != nil {
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
2022-02-14 14:22:44 +01:00
if !session.IsNew {
session.Options.MaxAge = -1
if err := auth.sessionStore.Save(r, rw, session); err != nil {
http.Error(rw, err.Error(), http.StatusInternalServerError)
return
}
}
onsuccess.ServeHTTP(rw, r)
})
}