Skip to content

Instantly share code, notes, and snippets.

@epetrie
Created June 5, 2013 03:29
Show Gist options
  • Save epetrie/5711416 to your computer and use it in GitHub Desktop.
Save epetrie/5711416 to your computer and use it in GitHub Desktop.
auth
internal static class AuthSessionManager
{
private static readonly TimeSpan DefaultRenewInterval = TimeSpan.FromSeconds(300);
private static readonly int AuthCheckMultiplier = 3;
private static readonly TimeoutDispatcher _dispatcher = new TimeoutDispatcher();
private static readonly Dictionary<string, AuthSession> _sessions = new Dictionary<string, AuthSession>();
private static readonly object LoginLock = new object();
private const int DefaultTokenValue = 1000;
private static readonly Dictionary<int, bool> TokenTable = new Dictionary<int, bool>();
static AuthSessionManager()
{
}
public static void GetExistingCachedSessions(IDVRSystem system)
{
var sessions = SessionCacheManager.GetSessions<AuthSession>(true, true);
if (sessions == null)
return;
foreach (var session in sessions)
AddCachedSession(system, session);
RefreshTokens();
}
private static void RenewCheck(IDVRSystem system, AuthSession session)
{
if (session.Expired)
{
lock (_sessions)
{
RemoveSession(session);
return;
}
}
// if the credentials for the DS connection become outdated/invalid we'll drop
// the session.
if (session.RenewChecks % AuthCheckMultiplier == 0)
{
if (!ValidateCredentials(system, session.User, session.Password))
{
// if the check using temp system failed and our main system is actually connected/logged in, that means the session should be removed.
// the extra check here is because we don't want to automatically remove sessions just because the DVR might be unreachable at the moment...
if (system.IsConnected)
{
lock (_sessions)
{
RemoveSession(session);
return;
}
}
}
session.SetValidated();
}
RenewSession(session);
_dispatcher.Add(() => session.CheckRenewal(system), EventLogger.LogException, DefaultRenewInterval);
}
public static int Login(IDVRSystem system, EvHeader header, string user, string pass)
{
lock (_sessions)
{
int token;
if (!Login(system, user, pass, out token))
return token;
header.userID = token;
var session = new AuthSession
{
CheckRenewalCallback = RenewCheck,
Header = header,
SessionId = token.ToString(),
User = user,
Password = pass,
RenewInterval = DefaultRenewInterval,
};
AddSession(system, session);
return token;
}
}
public static bool Logout(int token)
{
lock (_sessions)
{
var session = _sessions.Values.FirstOrDefault(s => s.SessionId == token.ToString());
if (session != null)
{
RemoveSession(session);
return true;
}
return false;
}
}
private static bool ValidateCredentials(IDVRSystem system, string user, string pass)
{
lock (LoginLock)
{
var sys = system as SharedDVRSystem;
return sys != null && sys.ValidateCredentials(user, pass);
}
}
public static EvHeader GetAuthenticationHeader()
{
var headers = OperationContext.Current.IncomingMessageHeaders;
EvHeader evHeader = null;
for (int i = 0; i < headers.Count; i++ )
{
var header = headers[i];
// handle the standard evHeader
if (header.Name == "evHeader")
{
evHeader = new EvHeader();
evHeader.ReadXml(headers.GetReaderAtHeader(i));
return evHeader;
}
//workaround for older SDK/Lenel/SWH implementations that omit the evHeader tag but include its members.
if (header.Name == "userID")
{
evHeader = new EvHeader();
evHeader.userID = Convert.ToInt32(headers.GetReaderAtHeader(i).ReadString());
}
if (header.Name == "UCN" && (evHeader != null))
{
evHeader.UCN = headers.GetReaderAtHeader(i).ReadString();
}
}
return evHeader;
}
public static void AuthenticateCurrentSoapRequest()
{
// "Many a securer profess their securees are secured securest,
// but securely securing our security is what truly secures us."
//
// -Dave McDonald's Time Traveling Ghost
//
// Securement of all that is securable. That's the PelcoAPI Way.
// "PelcoAPI - Securably Secure."
var header = GetAuthenticationHeader();
// special handling for the super secret endura UDN/id combo used by Endura Utilities and the Navy Seals.
if (IsSuperSecretHeader(header))
return;
// Per API team UCN is not a required field, and upon observation their SM Wrapper tool seems to include/exclude it at will in various versions.
// for example older versions do not honor the UCN, while newer versions honor UCN but do not honor the userID tag for logout requests...
var authenticated = _sessions.Values.FirstOrDefault(s => s.SessionId == header.userID.ToString());// && s.Header.UCN == header.UCN);
if (authenticated == null)
throw new SoapException("Session is not authenticated.", SoapException.ClientFaultCode);
RenewSession(authenticated, true);
}
private static bool Login(IDVRSystem system, string user, string pass, out int token)
{
var result = ValidateCredentials(system, user, pass);
token = result ? GetNewToken() : -1;
return result;
}
private static void AddSession(IDVRSystem system, AuthSession session)
{
if (_sessions.ContainsKey(session.SessionId))
{
RenewSession(session);
return;
}
SessionCacheManager.AddSession(session);
SetSession(system, session);
}
private static void AddCachedSession(IDVRSystem system, AuthSession session)
{
session.CheckRenewalCallback = RenewCheck;
SetSession(system, session);
}
private static void SetSession(IDVRSystem system, AuthSession session)
{
_sessions.Add(session.SessionId, session);
_dispatcher.Add(() => session.CheckRenewal(system), EventLogger.LogException, DefaultRenewInterval);
lock (TokenTable)
TokenTable[Convert.ToInt32(session.SessionId)] = true;
}
private static void RemoveSession(AuthSession session)
{
_sessions.Remove(session.SessionId);
SessionCacheManager.RemoveSession(session);
lock (TokenTable)
TokenTable[Convert.ToInt32(session.SessionId)] = false;
}
private static void RenewSession(AuthSession session, bool clientRequest = false)
{
session.Renew(clientRequest);
UpdateSession(session);
}
private static void UpdateSession(AuthSession session)
{
SessionCacheManager.UpdateSession(session);
SessionCacheManager.SaveCache();
}
private static bool IsSuperSecretHeader(EvHeader header)
{
// per API team any evHeader with userID of '0' is considered a 'magic' header...
//return header.userID == 0 && header.UCN == "uuid:5d26b142-d186-45ad-b67e-0f849ec05455";
return header.userID == 0;
}
private static void GenerateTokens()
{
lock (TokenTable)
{
for (var i = DefaultTokenValue; i < DefaultTokenValue * 2; i++)
TokenTable[i] = false;
}
}
private static void RefreshTokens()
{
lock (TokenTable)
{
GenerateTokens();
lock (_sessions)
{
foreach (var session in _sessions)
TokenTable[Convert.ToInt32(session.Value.SessionId)] = true;
}
}
}
private static int GetNewToken()
{
lock (TokenTable)
{
for (var i = DefaultTokenValue; i < DefaultTokenValue * 2; i++)
{
if (!TokenTable[i])
{
TokenTable[i] = true;
return i;
}
}
return -1;
}
}
}
internal class AuthSession : IXmlSerializableSession
{
public Action<IDVRSystem, AuthSession> CheckRenewalCallback { get; set; }
public EvHeader Header { get; set; }
public string User { get; set; }
public string Password { get; set; }
public DateTime LastUsed { get; private set; }
public bool Expired
{
get
{
// we'll keep sessions alive indefinitely as long as they are actually being used.
// if we go more than a day without a single use of the token we'll assume it's
// no longer being used.
return DateTime.Now >= this.NextRenewal &&
DateTime.Now.Subtract(this.LastUsed).TotalHours >= 24;
}
}
public int RenewChecks { get; private set; }
public AuthSession()
{
Renew(true);
}
public void Renew(bool clientRequest = false)
{
LastRenewal = DateTime.Now;
if (clientRequest)
LastUsed = DateTime.Now;
}
public void CheckRenewal(IDVRSystem system)
{
this.RenewChecks++;
if (this.CheckRenewalCallback != null)
this.CheckRenewalCallback(system, this);
}
public void SetValidated()
{
this.RenewChecks = 0;
}
#region Implementation of ITimedSession
public Type Type { get { return typeof(AuthSession); } }
public string SessionId { get; set; }
public DateTime LastRenewal { get; private set; }
public DateTime NextRenewal
{
get
{
if (LastRenewal == DateTime.MinValue)
return DateTime.MaxValue;
return LastRenewal.Add(RenewInterval);
}
}
private TimeSpan _renewInterval = TimeSpan.MaxValue;
public TimeSpan RenewInterval
{
get { return _renewInterval; }
set { _renewInterval = value; }
}
#endregion
#region Implementation of IXmlSerializable
public XmlSchema GetSchema()
{
throw new NotImplementedException();
}
public void ReadXml(XmlReader reader)
{
this.Header = new EvHeader();
new XmlStateMachine
{
{"evHeader", r => this.Header.ReadXml(reader)},
{"SessionId", r => this.SessionId = r.ReadString()},
{"User", r => this.User = PelcoBase64.Decode(r.ReadString())},
{"Password", r => this.Password = PelcoBase64.Decode(r.ReadString())},
{"RenewInterval", r => this.RenewInterval = TimeSpan.FromSeconds(Convert.ToDouble(r.ReadString()))},
{"LastRenewal", r => this.LastRenewal = DateTime.Parse(r.ReadString())},
{"LastUsed", r => this.LastUsed = DateTime.Parse(r.ReadString())},
reader
};
}
public void WriteXml(XmlWriter writer)
{
writer.WriteStartElement("AuthSession");
this.Header.WriteXml(writer);
writer.WriteElementString("SessionId", this.SessionId);
writer.WriteElementString("User", PelcoBase64.Encode(this.User));
writer.WriteElementString("Password", PelcoBase64.Encode(this.Password));
writer.WriteElementString("RenewInterval", this.RenewInterval.TotalSeconds.ToString());
writer.WriteElementString("LastRenewal", this.LastRenewal.ToString("s"));
writer.WriteElementString("LastUsed", this.LastUsed.ToString("s"));
writer.WriteEndElement();
}
#endregion
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment