123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170 |
- // 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() {
- logger.Debug(logSender, "", "oauth2 manager 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() {
- logger.Debug(logSender, "", "oauth2 manager cleanup")
- dataprovider.CleanupSharedSessions(dataprovider.SessionTypeOAuth2Auth, time.Now()) //nolint:errcheck
- }
|