Created
June 5, 2013 03:29
-
-
Save epetrie/5711416 to your computer and use it in GitHub Desktop.
auth
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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