/*
* Copyright (c) 2022 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.Security.Authentication;
using System.Runtime.CompilerServices;
using VNLib.Utils;
using VNLib.Net.Http;
using VNLib.Utils.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.
///
public readonly struct SessionInfo : IObjectStorage, IEquatable
{
///
/// A value indicating if the current instance has been initiailzed
/// with a session. Otherwise properties are undefied
///
public readonly bool IsSet;
private readonly ISession UserSession;
///
/// Key that identifies the current session. (Identical to cookie::sessionid)
///
public readonly string SessionID;
///
/// Session stored User-Agent
///
public readonly string UserAgent;
///
/// If the stored IP and current user's IP matches
///
public readonly bool IPMatch;
///
/// If the current connection and stored session have matching cross origin domains
///
public readonly bool CrossOriginMatch;
///
/// Flags the session as invalid. IMPORTANT: the user's session data is no longer valid and will throw an exception when accessed
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Invalidate(bool all = false) => UserSession.Invalidate(all);
///
/// Marks the session ID to be regenerated during closing event
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RegenID() => UserSession.RegenID();
///
public T GetObject(string key)
{
//Attempt to deserialze the object, or return default if it is empty
return this[key].AsJsonObject(SR_OPTIONS);
}
///
public void SetObject(string key, T obj)
{
//Serialize and store the object, or set null (remove) if the object is null
this[key] = obj?.ToJsonString(SR_OPTIONS);
}
///
/// Was the original session cross origin?
///
public readonly bool CrossOrigin;
///
/// The origin header specified during session creation
///
public readonly Uri SpecifiedOrigin;
///
/// Privilages associated with user specified during login
///
public readonly DateTimeOffset Created;
///
/// Was this session just created on this connection?
///
public readonly bool IsNew;
///
/// Gets or sets the session's login hash, if set to a non-empty/null value, will trigger an upgrade on close
///
public readonly string LoginHash
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => UserSession.GetLoginToken();
[MethodImpl(MethodImplOptions.AggressiveInlining)]
set => UserSession.SetLoginToken(value);
}
///
/// 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 code usually sets 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;
///
/// Was the session Initialy established on a secure connection?
///
public readonly SslProtocols SecurityProcol;
///
/// A value specifying the type of the backing session
///
public readonly SessionType SessionType => UserSession.SessionType;
///
/// 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;
//Calculate and store
IsNew = session.IsNew;
SessionID = session.SessionID;
Created = session.Created;
UserIP = session.UserIP;
//Ip match
IPMatch = trueIp.Equals(session.UserIP);
//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;
CrossOrigin = ci.CrossOrigin;
SecurityProcol = ci.SecurityProtocol;
}
else
{
//Load/decode stored variables
UserAgent = session.GetUserAgent();
SpecifiedOrigin = session.GetOriginUri();
CrossOrigin = session.IsCrossOrigin();
SecurityProcol = session.GetSecurityProtocol();
}
CrossOriginMatch = ci.Origin != null && ci.Origin.Equals(SpecifiedOrigin);
IsSet = true;
}
///
public bool Equals(SessionInfo other) => SessionID.Equals(other.SessionID, StringComparison.Ordinal);
///
public override bool Equals(object obj) => obj is SessionInfo si && Equals(si);
///
public 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);
}
}