| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251 |
- package otjwt
- import (
- "context"
- "crypto/rsa"
- "errors"
- "fmt"
- "os"
- "strings"
- "sync"
- "time"
- "github.com/MicahParks/keyfunc/v3"
- authTypes "github.com/OliveTin/OliveTin/internal/auth/authpublic"
- "github.com/OliveTin/OliveTin/internal/config"
- "github.com/golang-jwt/jwt/v5"
- log "github.com/sirupsen/logrus"
- )
- func parseJwtToken(cfg *config.Config, jwtString string) (*jwt.Token, error) {
- if cfg.AuthJwtCertsURL != "" {
- return parseJwtTokenWithRemoteKey(cfg, jwtString)
- }
- if cfg.AuthJwtPubKeyPath != "" {
- return parseJwtTokenWithLocalKey(cfg, jwtString)
- }
- if cfg.AuthJwtHmacSecret == "" {
- return nil, errors.New("no JWT authentication method configured")
- }
- return parseJwtTokenWithHMAC(cfg, jwtString)
- }
- func parserOptionsWithAudience(cfg *config.Config) []jwt.ParserOption {
- if cfg.AuthJwtAud == "" {
- return nil
- }
- return []jwt.ParserOption{jwt.WithAudience(cfg.AuthJwtAud)}
- }
- func getClaimsFromJwtToken(cfg *config.Config, jwtString string) (jwt.MapClaims, error) {
- token, err := parseJwtToken(cfg, jwtString)
- if err != nil {
- log.Errorf("jwt parse failure: %v", err)
- return nil, errors.New("jwt parse failure")
- }
- if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
- return claims, nil
- } else {
- return nil, errors.New("jwt token isn't valid")
- }
- }
- func parseJwtTokenWithRemoteKey(cfg *config.Config, jwtToken string) (*jwt.Token, error) {
- err := initJwks(cfg)
- if err != nil {
- log.Errorf("jwt init JWKS failure: %v", err)
- return nil, err
- }
- opts := parserOptionsWithAudience(cfg)
- return jwt.Parse(jwtToken, jwksVerifier.Keyfunc, opts...)
- }
- var (
- pubKeyBytes []byte = nil
- pubKey *rsa.PublicKey
- loadedKeyPath string
- jwksVerifier keyfunc.Keyfunc
- jwksOnce sync.Once
- jwksInitErr error
- localKeyMutex sync.RWMutex
- localKeyInitErr error
- )
- func initJwks(cfg *config.Config) error {
- jwksOnce.Do(func() {
- if cfg.AuthJwtCertsURL != "" {
- ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
- defer cancel()
- var err error
- jwksVerifier, err = keyfunc.NewDefaultCtx(ctx, []string{
- cfg.AuthJwtCertsURL,
- })
- if err != nil {
- log.Errorf("Init JWKS Failure: %v", err)
- jwksInitErr = err
- }
- }
- })
- return jwksInitErr
- }
- func loadPublicKeyFromFile(keyPath string) error {
- keyBytes, err := os.ReadFile(keyPath)
- if err != nil {
- return fmt.Errorf("couldn't read public key from file %s", keyPath)
- }
- parsedKey, err := jwt.ParseRSAPublicKeyFromPEM(keyBytes)
- if err != nil {
- return fmt.Errorf("error parsing public key object (from %s)", keyPath)
- }
- pubKeyBytes = keyBytes
- pubKey = parsedKey
- loadedKeyPath = keyPath
- localKeyInitErr = nil
- return nil
- }
- func isKeyLoadedForPath(keyPath string) bool {
- return pubKeyBytes != nil && loadedKeyPath == keyPath
- }
- func readLocalPublicKeyWithLock(keyPath string) error {
- localKeyMutex.RLock()
- alreadyLoaded := isKeyLoadedForPath(keyPath)
- localKeyMutex.RUnlock()
- if alreadyLoaded {
- return nil
- }
- localKeyMutex.Lock()
- defer localKeyMutex.Unlock()
- if isKeyLoadedForPath(keyPath) {
- return nil
- }
- localKeyInitErr = loadPublicKeyFromFile(keyPath)
- return localKeyInitErr
- }
- func readLocalPublicKey(cfg *config.Config) error {
- if cfg.AuthJwtPubKeyPath == "" {
- return errors.New("no JWT public key path configured")
- }
- return readLocalPublicKeyWithLock(cfg.AuthJwtPubKeyPath)
- }
- func parseJwtTokenWithLocalKey(cfg *config.Config, jwtString string) (*jwt.Token, error) {
- err := readLocalPublicKey(cfg)
- if err != nil {
- return nil, err
- }
- keyFunc := func(token *jwt.Token) (interface{}, error) {
- if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
- return nil, fmt.Errorf("parseJwt expected token algorithm RSA but got: %v", token.Header["alg"])
- }
- return pubKey, nil
- }
- opts := parserOptionsWithAudience(cfg)
- return jwt.Parse(jwtString, keyFunc, opts...)
- }
- // Hash-based Message Authentication Code
- func parseJwtTokenWithHMAC(cfg *config.Config, jwtString string) (*jwt.Token, error) {
- keyFunc := func(token *jwt.Token) (interface{}, error) {
- if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
- return nil, fmt.Errorf("parseJwt expected token algorithm HMAC but got: %v", token.Header["alg"])
- }
- return []byte(cfg.AuthJwtHmacSecret), nil
- }
- opts := parserOptionsWithAudience(cfg)
- return jwt.Parse(jwtString, keyFunc, opts...)
- }
- func lookupClaimValueOrDefault(claims jwt.MapClaims, key string, def string) string {
- if val, ok := claims[key]; ok {
- return fmt.Sprintf("%s", val)
- } else {
- return def
- }
- }
- func CheckUserFromJwtCookie(context *authTypes.AuthCheckingContext) *authTypes.AuthenticatedUser {
- cookie, err := context.Request.Cookie(context.Config.AuthJwtCookieName)
- if err != nil {
- log.Debugf("jwt cookie check %v name: %v", err, context.Config.AuthJwtCookieName)
- return nil
- }
- return parseJwt(context.Config, cookie.Value)
- }
- func CheckUserFromJwtHeader(context *authTypes.AuthCheckingContext) *authTypes.AuthenticatedUser {
- header := context.Request.Header.Get(context.Config.AuthJwtHeader)
- if header == "" {
- return nil
- }
- token := strings.TrimPrefix(header, "Bearer ")
- token = strings.TrimSpace(token)
- return parseJwt(context.Config, token)
- }
- func parseJwt(cfg *config.Config, token string) *authTypes.AuthenticatedUser {
- claims, err := getClaimsFromJwtToken(cfg, token)
- if err != nil {
- log.Warnf("jwt claim error: %+v", err)
- return nil
- }
- if cfg.InsecureAllowDumpJwtClaims {
- log.Debugf("JWT Claims %+v", claims)
- }
- user := &authTypes.AuthenticatedUser{
- Username: lookupClaimValueOrDefault(claims, cfg.AuthJwtClaimUsername, ""),
- UsergroupLine: parseGroupClaim(cfg.AuthJwtClaimUserGroup, claims),
- Provider: "jwt",
- }
- return user
- }
- func parseGroupClaim(groupClaim string, claims jwt.MapClaims) string {
- usergroup := ""
- if val, ok := claims[groupClaim]; ok {
- if array, ok := val.([]interface{}); ok {
- groups := make([]string, len(array))
- for i, v := range array {
- groups[i] = fmt.Sprintf("%s", v)
- }
- usergroup = strings.Join(groups, " ")
- } else {
- usergroup = fmt.Sprintf("%s", val)
- }
- }
- return usergroup
- }
|