/* * 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); } }