/*
* Copyright (c) 2023 Vaughn Nugent
*
* Library: VNLib
* Package: VNLib.Plugins.Essentials
* File: SessionInfo.cs
*
* SessionInfo.cs is part of VNLib.Plugins.Essentials which is part of the larger
* VNLib collection of libraries and utilities.
*
* VNLib.Plugins.Essentials 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, either version 3 of the
* License, or (at your option) any later version.
*
* VNLib.Plugins.Essentials 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/.
*/
using System;
using System.Net;
using System.Text.Json;
using System.Security.Authentication;
using System.Runtime.CompilerServices;
using VNLib.Utils;
using VNLib.Net.Http;
using VNLib.Plugins.Essentials.Extensions;
using static VNLib.Plugins.Essentials.Statics;
/*
* SessionInfo is a structure since it is only meant used in
* an HttpEntity context, so it may be allocated as part of
* the HttpEntity object, so have a single larger object
* passed by ref, and created once per request. It may even
* be cached and reused in the future. But for now user-apis
* should not be cached until a safe use policy is created.
*/
#pragma warning disable CA1051 // Do not declare visible instance fields
namespace VNLib.Plugins.Essentials.Sessions
{
///
/// When attached to a connection, provides persistant session storage and inforamtion based
/// on a connection.
///
///
/// This structure should not be stored and should not be accessed when the parent http entity
/// has been closed.
///
public readonly struct SessionInfo : IObjectStorage, IEquatable
{
/*
* Store status flags as a 1 byte enum
*/
[Flags]
private enum SessionFlags : byte
{
None = 0x00,
IsSet = 0x01,
IpMatch = 0x02
}
private readonly ISession UserSession;
private readonly SessionFlags _flags;
///
/// A value indicating if the current instance has been initiailzed
/// with a session. Otherwise properties are undefied
///
public readonly bool IsSet
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _flags.HasFlag(SessionFlags.IsSet);
}
///
/// The origin header specified during session creation
///
public readonly Uri? SpecifiedOrigin;
///
/// Was the session Initialy established on a secure connection?
///
public readonly SslProtocols SecurityProcol;
///
/// Session stored User-Agent
///
public readonly string? UserAgent;
///
/// Key that identifies the current session. (Identical to cookie::sessionid)
///
public readonly string SessionID
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => UserSession.SessionID;
}
///
/// If the stored IP and current user's IP matches
///
public readonly bool IPMatch
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _flags.HasFlag(SessionFlags.IpMatch);
}
///
/// Was this session just created on this connection?
///
public readonly bool IsNew
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => UserSession.IsNew;
}
///
/// The time the session was created
///
public readonly DateTimeOffset Created
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => UserSession.Created;
}
///
/// Gets or sets the session's login token, if set to a non-empty/null value, will trigger an upgrade on close
///
public readonly string Token
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => UserSession.Token;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set => UserSession.Token = value;
}
///
///
/// Gets or sets the user-id for the current session.
///
///
/// Login routines usually set this value and it should be read-only
///
///
public readonly string UserID
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => UserSession.UserID;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set => UserSession.UserID = value;
}
///
/// Privilages associated with user specified during login
///
public readonly ulong Privilages
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => UserSession.Privilages;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set => UserSession.Privilages = value;
}
///
/// The IP address belonging to the client
///
public readonly IPAddress UserIP
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => UserSession.UserIP;
}
///
/// A value specifying the type of the backing session
///
public readonly SessionType SessionType
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => UserSession.SessionType;
}
///
/// Flags the session as invalid. IMPORTANT: the user's session data is no longer valid, no data
/// will be saved to the session store when the session closes
///
public readonly void Invalidate(bool all = false) => UserSession.Invalidate(all);
///
/// Marks the session ID to be regenerated during closing event
///
public readonly void RegenID() => UserSession.RegenID();
///
/// Marks the session to be detached from the current connection.
///
public readonly void Detach() => UserSession.Detach();
#nullable disable
///
public T GetObject(string key) => JsonSerializer.Deserialize(this[key], SR_OPTIONS);
///
public void SetObject(string key, T obj) => this[key] = obj == null ? null: JsonSerializer.Serialize(obj, SR_OPTIONS);
#nullable enable
///
/// Accesses the session's general storage
///
/// Key for specifie data
/// Value associated with the key from the session's general storage
public readonly string this[string index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => UserSession[index];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set => UserSession[index] = value;
}
internal SessionInfo(ISession session, IConnectionInfo ci, IPAddress trueIp)
{
UserSession = session;
_flags |= SessionFlags.IsSet;
//Set ip match flag if current ip and stored ip match
_flags |= trueIp.Equals(session.UserIP) ? SessionFlags.IpMatch : SessionFlags.None;
//If the session is new, we can store intial security variables
if (session.IsNew)
{
session.InitNewSession(ci);
//Since all values will be the same as the connection, cache the connection values
UserAgent = ci.UserAgent;
SpecifiedOrigin = ci.Origin;
SecurityProcol = ci.GetSslProtocol();
}
else
{
//Load/decode stored variables
UserAgent = session.GetUserAgent();
SpecifiedOrigin = session.GetOriginUri();
SecurityProcol = session.GetSecurityProtocol();
}
}
///
public readonly bool Equals(SessionInfo other) => SessionID.Equals(other.SessionID, StringComparison.Ordinal);
///
public readonly override bool Equals(object? obj) => obj is SessionInfo si && Equals(si);
///
public readonly override int GetHashCode() => SessionID.GetHashCode(StringComparison.Ordinal);
///
public static bool operator ==(SessionInfo left, SessionInfo right) => left.Equals(right);
///
public static bool operator !=(SessionInfo left, SessionInfo right) => !(left == right);
}
}