| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170 | 
							- // Copyright (C) 2019 Nicola Murino
 
- //
 
- // This program is free software: you can redistribute it and/or modify
 
- // it under the terms of the GNU Affero General Public License as published
 
- // by the Free Software Foundation, version 3.
 
- //
 
- // This program is distributed in the hope that it will be useful,
 
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
 
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 
- // GNU Affero General Public License for more details.
 
- //
 
- // You should have received a copy of the GNU Affero General Public License
 
- // along with this program. If not, see <https://www.gnu.org/licenses/>.
 
- package httpd
 
- import (
 
- 	"crypto/sha256"
 
- 	"encoding/hex"
 
- 	"encoding/json"
 
- 	"errors"
 
- 	"sync"
 
- 	"time"
 
- 	"github.com/drakkan/sftpgo/v2/internal/dataprovider"
 
- 	"github.com/drakkan/sftpgo/v2/internal/kms"
 
- 	"github.com/drakkan/sftpgo/v2/internal/logger"
 
- 	"github.com/drakkan/sftpgo/v2/internal/util"
 
- )
 
- var (
 
- 	oauth2Mgr oauth2Manager
 
- )
 
- func newOAuth2Manager(isShared int) oauth2Manager {
 
- 	if isShared == 1 {
 
- 		logger.Info(logSender, "", "using provider OAuth2 manager")
 
- 		return &dbOAuth2Manager{}
 
- 	}
 
- 	logger.Info(logSender, "", "using memory OAuth2 manager")
 
- 	return &memoryOAuth2Manager{
 
- 		pendingAuths: make(map[string]oauth2PendingAuth),
 
- 	}
 
- }
 
- type oauth2PendingAuth struct {
 
- 	State        string      `json:"state"`
 
- 	Provider     int         `json:"provider"`
 
- 	ClientID     string      `json:"client_id"`
 
- 	ClientSecret *kms.Secret `json:"client_secret"`
 
- 	RedirectURL  string      `json:"redirect_url"`
 
- 	IssuedAt     int64       `json:"issued_at"`
 
- }
 
- func newOAuth2PendingAuth(provider int, redirectURL, clientID string, clientSecret *kms.Secret) oauth2PendingAuth {
 
- 	state := sha256.Sum256(util.GenerateRandomBytes(32))
 
- 	return oauth2PendingAuth{
 
- 		State:        hex.EncodeToString(state[:]),
 
- 		Provider:     provider,
 
- 		ClientID:     clientID,
 
- 		ClientSecret: clientSecret,
 
- 		RedirectURL:  redirectURL,
 
- 		IssuedAt:     util.GetTimeAsMsSinceEpoch(time.Now()),
 
- 	}
 
- }
 
- type oauth2Manager interface {
 
- 	addPendingAuth(pendingAuth oauth2PendingAuth)
 
- 	removePendingAuth(state string)
 
- 	getPendingAuth(state string) (oauth2PendingAuth, error)
 
- 	cleanup()
 
- }
 
- type memoryOAuth2Manager struct {
 
- 	mu           sync.RWMutex
 
- 	pendingAuths map[string]oauth2PendingAuth
 
- }
 
- func (o *memoryOAuth2Manager) addPendingAuth(pendingAuth oauth2PendingAuth) {
 
- 	o.mu.Lock()
 
- 	defer o.mu.Unlock()
 
- 	o.pendingAuths[pendingAuth.State] = pendingAuth
 
- }
 
- func (o *memoryOAuth2Manager) removePendingAuth(state string) {
 
- 	o.mu.Lock()
 
- 	defer o.mu.Unlock()
 
- 	delete(o.pendingAuths, state)
 
- }
 
- func (o *memoryOAuth2Manager) getPendingAuth(state string) (oauth2PendingAuth, error) {
 
- 	o.mu.RLock()
 
- 	defer o.mu.RUnlock()
 
- 	authReq, ok := o.pendingAuths[state]
 
- 	if !ok {
 
- 		return oauth2PendingAuth{}, errors.New("oauth2: no auth request found for the specified state")
 
- 	}
 
- 	diff := util.GetTimeAsMsSinceEpoch(time.Now()) - authReq.IssuedAt
 
- 	if diff > authStateValidity {
 
- 		return oauth2PendingAuth{}, errors.New("oauth2: auth request is too old")
 
- 	}
 
- 	return authReq, nil
 
- }
 
- func (o *memoryOAuth2Manager) cleanup() {
 
- 	o.mu.Lock()
 
- 	defer o.mu.Unlock()
 
- 	for k, auth := range o.pendingAuths {
 
- 		diff := util.GetTimeAsMsSinceEpoch(time.Now()) - auth.IssuedAt
 
- 		// remove old pending auth requests
 
- 		if diff < 0 || diff > authStateValidity {
 
- 			delete(o.pendingAuths, k)
 
- 		}
 
- 	}
 
- }
 
- type dbOAuth2Manager struct{}
 
- func (o *dbOAuth2Manager) addPendingAuth(pendingAuth oauth2PendingAuth) {
 
- 	if err := pendingAuth.ClientSecret.Encrypt(); err != nil {
 
- 		logger.Error(logSender, "", "unable to encrypt oauth2 secret: %v", err)
 
- 		return
 
- 	}
 
- 	session := dataprovider.Session{
 
- 		Key:       pendingAuth.State,
 
- 		Data:      pendingAuth,
 
- 		Type:      dataprovider.SessionTypeOAuth2Auth,
 
- 		Timestamp: pendingAuth.IssuedAt + authStateValidity,
 
- 	}
 
- 	dataprovider.AddSharedSession(session) //nolint:errcheck
 
- }
 
- func (o *dbOAuth2Manager) removePendingAuth(state string) {
 
- 	dataprovider.DeleteSharedSession(state) //nolint:errcheck
 
- }
 
- func (o *dbOAuth2Manager) getPendingAuth(state string) (oauth2PendingAuth, error) {
 
- 	session, err := dataprovider.GetSharedSession(state)
 
- 	if err != nil {
 
- 		return oauth2PendingAuth{}, errors.New("oauth2: unable to get the auth request for the specified state")
 
- 	}
 
- 	if session.Timestamp < util.GetTimeAsMsSinceEpoch(time.Now()) {
 
- 		// expired
 
- 		return oauth2PendingAuth{}, errors.New("oauth2: auth request is too old")
 
- 	}
 
- 	return o.decodePendingAuthData(session.Data)
 
- }
 
- func (o *dbOAuth2Manager) decodePendingAuthData(data any) (oauth2PendingAuth, error) {
 
- 	if val, ok := data.([]byte); ok {
 
- 		authReq := oauth2PendingAuth{}
 
- 		err := json.Unmarshal(val, &authReq)
 
- 		if err != nil {
 
- 			return authReq, err
 
- 		}
 
- 		err = authReq.ClientSecret.TryDecrypt()
 
- 		return authReq, err
 
- 	}
 
- 	logger.Error(logSender, "", "invalid oauth2 auth request data type %T", data)
 
- 	return oauth2PendingAuth{}, errors.New("oauth2: invalid auth request data")
 
- }
 
- func (o *dbOAuth2Manager) cleanup() {
 
- 	dataprovider.CleanupSharedSessions(dataprovider.SessionTypeOAuth2Auth, time.Now()) //nolint:errcheck
 
- }
 
 
  |