mirror of
https://github.com/drakkan/sftpgo.git
synced 2024-11-22 23:50:32 +00:00
51febb19fa
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
168 lines
4.9 KiB
Go
168 lines
4.9 KiB
Go
// Copyright (C) 2019-2023 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 (
|
|
"encoding/json"
|
|
"errors"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/rs/xid"
|
|
|
|
"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 {
|
|
return oauth2PendingAuth{
|
|
State: xid.New().String(),
|
|
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
|
|
}
|