github.com/cs3org/reva/v2@v2.27.7/pkg/siteacc/html/sessionmanager.go (about)

     1  // Copyright 2018-2020 CERN
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  // In applying this license, CERN does not waive the privileges and immunities
    16  // granted to it by virtue of its status as an Intergovernmental Organization
    17  // or submit itself to any jurisdiction.
    18  
    19  package html
    20  
    21  import (
    22  	"fmt"
    23  	"net/http"
    24  	"sync"
    25  	"time"
    26  
    27  	"github.com/cs3org/reva/v2/pkg/siteacc/config"
    28  	"github.com/pkg/errors"
    29  	"github.com/rs/zerolog"
    30  )
    31  
    32  // SessionManager manages HTML sessions.
    33  type SessionManager struct {
    34  	conf *config.Configuration
    35  	log  *zerolog.Logger
    36  
    37  	sessions map[string]*Session
    38  
    39  	sessionName string
    40  
    41  	mutex sync.Mutex
    42  }
    43  
    44  func (mngr *SessionManager) initialize(name string, conf *config.Configuration, log *zerolog.Logger) error {
    45  	if name == "" {
    46  		return errors.Errorf("no session name provided")
    47  	}
    48  	mngr.sessionName = name
    49  
    50  	if conf == nil {
    51  		return errors.Errorf("no configuration provided")
    52  	}
    53  	mngr.conf = conf
    54  
    55  	if log == nil {
    56  		return errors.Errorf("no logger provided")
    57  	}
    58  	mngr.log = log
    59  
    60  	mngr.sessions = make(map[string]*Session, 100)
    61  
    62  	return nil
    63  }
    64  
    65  // HandleRequest performs all session-related tasks during an HTML request. Always returns a valid session object.
    66  func (mngr *SessionManager) HandleRequest(w http.ResponseWriter, r *http.Request) (*Session, error) {
    67  	mngr.mutex.Lock()
    68  	defer mngr.mutex.Unlock()
    69  
    70  	var session *Session
    71  	var sessionErr error
    72  
    73  	// Try to get the session ID from the request; if none has been set yet, a new one will be assigned
    74  	cookie, err := r.Cookie(mngr.sessionName)
    75  	if err == nil {
    76  		session = mngr.findSession(cookie.Value)
    77  		if session != nil {
    78  			mngr.logSessionInfo(session, r, "existing session found")
    79  
    80  			// Verify the request against the session: If it is invalid, set an error; if the session has expired, create a new one; if it has already passed its halftime, migrate to a new one
    81  			if err := session.VerifyRequest(r, mngr.conf.Webserver.VerifyRemoteAddress); err == nil {
    82  				if session.HasExpired() {
    83  					// The session has expired, so a new one needs to be created
    84  					session = nil
    85  
    86  					mngr.logSessionInfo(session, r, "session expired")
    87  				} else if session.HalftimePassed() {
    88  					// The session has passed its halftime, so migrate it to a new one (makes hijacking session IDs harder)
    89  					session, err = mngr.migrateSession(session, r)
    90  					if err != nil {
    91  						session = nil
    92  						sessionErr = errors.Wrap(err, "unable to migrate session")
    93  					}
    94  
    95  					mngr.logSessionInfo(session, r, "session migrated")
    96  				}
    97  			} else {
    98  				session = nil
    99  				sessionErr = errors.Wrap(err, "invalid session")
   100  
   101  				mngr.logSessionInfo(session, r, "session invalid (verify failed)")
   102  			}
   103  		}
   104  	} else if err != http.ErrNoCookie {
   105  		// The session cookie exists but seems to be invalid, so set an error
   106  		session = nil
   107  		sessionErr = errors.Wrap(err, "unable to get the session ID from the client")
   108  
   109  		mngr.logSessionInfo(session, r, fmt.Sprintf("session cookie error: %v", err))
   110  	}
   111  
   112  	if session == nil {
   113  		// No session found for the client, so create a new one; this will always succeed
   114  		session = mngr.createSession(r)
   115  
   116  		mngr.logSessionInfo(session, r, "assigned new session")
   117  	}
   118  
   119  	// Store the session ID on the client side
   120  	session.Save(mngr.conf.Webserver.URL, w)
   121  
   122  	return session, sessionErr
   123  }
   124  
   125  // PurgeSessions removes any expired sessions.
   126  func (mngr *SessionManager) PurgeSessions() {
   127  	mngr.mutex.Lock()
   128  	defer mngr.mutex.Unlock()
   129  
   130  	var expiredSessions []string
   131  	for id, session := range mngr.sessions {
   132  		if session.HasExpired() {
   133  			expiredSessions = append(expiredSessions, id)
   134  		}
   135  	}
   136  
   137  	for _, id := range expiredSessions {
   138  		delete(mngr.sessions, id)
   139  	}
   140  }
   141  
   142  func (mngr *SessionManager) createSession(r *http.Request) *Session {
   143  	session := NewSession(mngr.sessionName, time.Duration(mngr.conf.Webserver.SessionTimeout)*time.Second, r)
   144  	mngr.sessions[session.ID] = session
   145  	return session
   146  }
   147  
   148  func (mngr *SessionManager) findSession(id string) *Session {
   149  	if session, ok := mngr.sessions[id]; ok {
   150  		return session
   151  	}
   152  	return nil
   153  }
   154  
   155  func (mngr *SessionManager) migrateSession(session *Session, r *http.Request) (*Session, error) {
   156  	sessionNew := mngr.createSession(r)
   157  
   158  	// Carry over the old session information, thus preserving the existing session
   159  	sessionNew.MigrationID = session.ID
   160  	sessionNew.Data = session.Data
   161  
   162  	if user := session.LoggedInUser(); user != nil {
   163  		sessionNew.LoginUser(user.Account, user.Site)
   164  	} else {
   165  		sessionNew.LogoutUser()
   166  	}
   167  
   168  	// Delete the old session
   169  	delete(mngr.sessions, session.ID)
   170  
   171  	return sessionNew, nil
   172  }
   173  
   174  func (mngr *SessionManager) logSessionInfo(session *Session, r *http.Request, info string) {
   175  	if mngr.conf.Webserver.LogSessions {
   176  		if session != nil {
   177  			mngr.log.Debug().Str("id", session.ID).Str("address", r.RemoteAddr).Str("path", r.URL.Path).Msg(info)
   178  		} else {
   179  			mngr.log.Debug().Str("address", r.RemoteAddr).Str("path", r.URL.Path).Msg(info)
   180  		}
   181  	}
   182  }
   183  
   184  // NewSessionManager creates a new session manager.
   185  func NewSessionManager(name string, conf *config.Configuration, log *zerolog.Logger) (*SessionManager, error) {
   186  	mngr := &SessionManager{}
   187  	if err := mngr.initialize(name, conf, log); err != nil {
   188  		return nil, errors.Wrap(err, "unable to initialize the session manager")
   189  	}
   190  	return mngr, nil
   191  }