1
0
mirror of https://github.com/oauth2-proxy/oauth2-proxy.git synced 2025-01-24 05:26:55 +02:00
oauth2-proxy/pkg/apis/sessions/session_state.go
Nick Meves f7cca1d0b3
Refactor encryption.Cipher to be an Encrypt/Decrypt Interface
All Encrypt/Decrypt Cipher implementations will now take
and return []byte to set up usage in future binary compatible
encoding schemes to fix issues with bloat encrypting to strings
(which requires base64ing adding 33% size)
2020-06-12 14:36:58 -07:00

151 lines
3.8 KiB
Go

package sessions
import (
"encoding/json"
"errors"
"fmt"
"time"
"unicode/utf8"
"github.com/oauth2-proxy/oauth2-proxy/pkg/encryption"
)
// SessionState is used to store information about the currently authenticated user session
type SessionState struct {
AccessToken string `json:",omitempty"`
IDToken string `json:",omitempty"`
CreatedAt *time.Time `json:",omitempty"`
ExpiresOn *time.Time `json:",omitempty"`
RefreshToken string `json:",omitempty"`
Email string `json:",omitempty"`
User string `json:",omitempty"`
PreferredUsername string `json:",omitempty"`
}
// IsExpired checks whether the session has expired
func (s *SessionState) IsExpired() bool {
if s.ExpiresOn != nil && !s.ExpiresOn.IsZero() && s.ExpiresOn.Before(time.Now()) {
return true
}
return false
}
// Age returns the age of a session
func (s *SessionState) Age() time.Duration {
if s.CreatedAt != nil && !s.CreatedAt.IsZero() {
return time.Now().Truncate(time.Second).Sub(*s.CreatedAt)
}
return 0
}
// String constructs a summary of the session state
func (s *SessionState) String() string {
o := fmt.Sprintf("Session{email:%s user:%s PreferredUsername:%s", s.Email, s.User, s.PreferredUsername)
if s.AccessToken != "" {
o += " token:true"
}
if s.IDToken != "" {
o += " id_token:true"
}
if !s.CreatedAt.IsZero() {
o += fmt.Sprintf(" created:%s", s.CreatedAt)
}
if !s.ExpiresOn.IsZero() {
o += fmt.Sprintf(" expires:%s", s.ExpiresOn)
}
if s.RefreshToken != "" {
o += " refresh_token:true"
}
return o + "}"
}
// EncodeSessionState returns string representation of the current session
func (s *SessionState) EncodeSessionState(c encryption.Cipher) (string, error) {
var ss SessionState
if c == nil {
// Store only Email and User when cipher is unavailable
ss.Email = s.Email
ss.User = s.User
ss.PreferredUsername = s.PreferredUsername
} else {
ss = *s
for _, s := range []*string{
&ss.Email,
&ss.User,
&ss.PreferredUsername,
&ss.AccessToken,
&ss.IDToken,
&ss.RefreshToken,
} {
err := c.EncryptInto(s)
if err != nil {
return "", err
}
}
}
b, err := json.Marshal(ss)
return string(b), err
}
// DecodeSessionState decodes the session cookie string into a SessionState
func DecodeSessionState(v string, c encryption.Cipher) (*SessionState, error) {
var ss SessionState
err := json.Unmarshal([]byte(v), &ss)
if err != nil {
return nil, fmt.Errorf("error unmarshalling session: %w", err)
}
if c == nil {
// Load only Email and User when cipher is unavailable
ss = SessionState{
Email: ss.Email,
User: ss.User,
PreferredUsername: ss.PreferredUsername,
}
} else {
// Backward compatibility with using unencrypted Email
if ss.Email != "" {
decryptedEmail, errEmail := stringDecrypt(ss.Email, c)
if errEmail == nil {
if !utf8.ValidString(decryptedEmail) {
return nil, errors.New("invalid value for decrypted email")
}
ss.Email = decryptedEmail
}
}
// Backward compatibility with using unencrypted User
if ss.User != "" {
decryptedUser, errUser := stringDecrypt(ss.User, c)
if errUser == nil {
if !utf8.ValidString(decryptedUser) {
return nil, errors.New("invalid value for decrypted user")
}
ss.User = decryptedUser
}
}
for _, s := range []*string{
&ss.PreferredUsername,
&ss.AccessToken,
&ss.IDToken,
&ss.RefreshToken,
} {
err := c.DecryptInto(s)
if err != nil {
return nil, err
}
}
}
return &ss, nil
}
// stringDecrypt wraps a Base64Cipher to make it string => string
func stringDecrypt(ciphertext string, c encryption.Cipher) (string, error) {
value, err := c.Decrypt([]byte(ciphertext))
if err != nil {
return "", err
}
return string(value), nil
}