/// <summary>
        /// Authenticate the user
        /// </summary>
        public IPrincipal Authenticate(string userName, string password)
        {
            var evt = new AuthenticatingEventArgs(userName);

            this.Authenticating?.Invoke(this, evt);
            if (evt.Cancel)
            {
                throw new SecurityException("Authentication cancelled");
            }

            try
            {
                var principal = AdoClaimsIdentity.Create(userName, password).CreateClaimsPrincipal();

                this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(userName, principal, true));
                return(principal);
            }
            catch (AuthenticationException e) when(e.Message.Contains("AUTH_LCK"))
            {
                throw new AuthenticationException($"Account is locked please contact an administrator");
            }
            catch (AuthenticationException e) when(e.Message.Contains("AUTH_INV"))
            {
                throw new AuthenticationException($"Invalid username or password");
            }
            catch (Exception e)
            {
                this.m_traceSource.TraceEvent(EventLevel.Verbose, "Invalid credentials : {0}/{1}", userName, password);

                this.m_traceSource.TraceEvent(EventLevel.Error, e.ToString());
                this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(userName, null, false));
                throw new AuthenticationException($"General exception authenticating {userName}", e);
            }
        }
        /// <summary>
        /// Authenticate the user
        /// </summary>
        public IPrincipal Authenticate(string userName, string password)
        {
            var evt = new AuthenticatingEventArgs(userName);

            this.Authenticating?.Invoke(this, evt);
            if (evt.Cancel)
            {
                throw new SecurityException("Authentication cancelled");
            }

            try
            {
                var principal = AdoClaimsIdentity.Create(userName, password).CreateClaimsPrincipal();

                this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(userName, principal, true));
                return(principal);
            }
            catch (Exception e)
            {
                this.m_traceSource.TraceEvent(EventLevel.Verbose, "Invalid credentials : {0}/{1}", userName, password);

                this.m_traceSource.TraceEvent(EventLevel.Error, e.ToString());
                this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(userName, null, false));
                throw;
            }
        }
        /// <summary>
        /// Create a basic user
        /// </summary>
        public IIdentity CreateIdentity(string userName, string password, IPrincipal principal)
        {
            this.VerifyPrincipal(principal, PermissionPolicyIdentifiers.CreateIdentity);

            if (String.IsNullOrEmpty(userName))
            {
                throw new ArgumentNullException(nameof(userName));
            }
            else if (String.IsNullOrEmpty(password))
            {
                throw new ArgumentNullException(nameof(password));
            }

            this.m_traceSource.TraceInfo("Creating identity {0} ({1})", userName, principal);

            try
            {
                using (var dataContext = this.m_configuration.Provider.GetWriteConnection())
                {
                    dataContext.Open();

                    using (var tx = dataContext.BeginTransaction())
                        try
                        {
                            var hashingService = ApplicationServiceContext.Current.GetService <IPasswordHashingService>();
                            var pdpService     = ApplicationServiceContext.Current.GetService <IPolicyDecisionService>();

                            // Demand create identity
                            new PolicyPermission(System.Security.Permissions.PermissionState.Unrestricted, PermissionPolicyIdentifiers.CreateIdentity).Demand();

                            // Does this principal have the ability to
                            DbSecurityUser newIdentityUser = new DbSecurityUser()
                            {
                                UserName             = userName,
                                Password             = hashingService.ComputeHash(password),
                                SecurityHash         = Guid.NewGuid().ToString(),
                                UserClass            = UserClassKeys.HumanUser,
                                InvalidLoginAttempts = 0
                            };
                            newIdentityUser.CreatedByKey = dataContext.EstablishProvenance(principal, null);

                            dataContext.Insert(newIdentityUser);
                            var retVal = AdoClaimsIdentity.Create(newIdentityUser);
                            tx.Commit();
                            return(retVal);
                        }
                        catch
                        {
                            tx.Rollback();
                            throw;
                        }
                }
            }
            catch (Exception e)
            {
                this.m_traceSource.TraceEvent(EventLevel.Error, e.ToString());
                throw;
            }
        }
        /// <summary>
        /// Authenticate this user based on the session
        /// </summary>
        /// <param name="session">The session for which authentication is being saught</param>
        /// <returns>The authenticated principal</returns>
        public IPrincipal Authenticate(ISession session)
        {
            try
            {
                using (var context = this.m_configuration.Provider.GetReadonlyConnection())
                {
                    context.Open();
                    var sessionId = new Guid(session.Id.Take(16).ToArray());

                    var sql = context.CreateSqlStatement <DbSession>().SelectFrom(typeof(DbSession), typeof(DbSecurityApplication), typeof(DbSecurityUser), typeof(DbSecurityDevice))
                              .InnerJoin <DbSecurityApplication>(o => o.ApplicationKey, o => o.Key)
                              .Join <DbSession, DbSecurityUser>("LEFT", o => o.UserKey, o => o.Key)
                              .Join <DbSession, DbSecurityDevice>("LEFT", o => o.DeviceKey, o => o.Key)
                              .Where <DbSession>(s => s.Key == sessionId);

                    var auth = context.FirstOrDefault <CompositeResult <DbSession, DbSecurityApplication, DbSecurityUser, DbSecurityDevice> >(sql);

                    // Identities
                    List <IClaimsIdentity> identities = new List <IClaimsIdentity>(3);
                    if (auth.Object1.NotAfter < DateTime.Now)
                    {
                        throw new AuthenticationException("Session is expired");
                    }
                    if (auth.Object2?.Key != null)
                    {
                        identities.Add(new Core.Security.ApplicationIdentity(auth.Object2.Key, auth.Object2.PublicId, true));
                    }
                    if (auth.Object1.DeviceKey.HasValue)
                    {
                        identities.Add(new DeviceIdentity(auth.Object4.Key, auth.Object4.PublicId, true));
                    }

                    var principal = auth.Object1.UserKey.GetValueOrDefault() == Guid.Empty ?
                                    new SanteDBClaimsPrincipal(identities) : AdoClaimsIdentity.Create(auth.Object3, true, "SESSION").CreateClaimsPrincipal(identities);

                    identities.First().AddClaim(new SanteDBClaim(SanteDBClaimTypes.AuthenticationInstant, session.NotBefore.ToString("o")));
                    identities.First().AddClaim(new SanteDBClaim(SanteDBClaimTypes.Expiration, session.NotAfter.ToString("o")));
                    identities.First().AddClaim(new SanteDBClaim(SanteDBClaimTypes.SanteDBSessionIdClaim, auth.Object1.Key.ToString()));

                    // Add claims from session
                    foreach (var clm in session.Claims)
                    {
                        identities.First().AddClaim(clm);
                    }

                    // TODO: Load additional claims made about the user on the session
                    return(principal);
                }
            }
            catch (Exception e)
            {
                this.m_traceSource.TraceEvent(EventLevel.Verbose, "Invalid session auth: {0}", e.Message);
                this.m_traceSource.TraceEvent(EventLevel.Error, e.ToString());
                this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(null, null, false));
                throw;
            }
        }
        /// <summary>
        /// Get the specified identities from the session
        /// </summary>
        public IIdentity[] GetIdentities(ISession session)
        {
            try
            {
                using (var context = this.m_configuration.Provider.GetReadonlyConnection())
                {
                    context.Open();
                    var sessionId = new Guid(session.Id.Take(16).ToArray());
                    var sql       = context.CreateSqlStatement <DbSession>().SelectFrom(typeof(DbSession), typeof(DbSecurityApplication), typeof(DbSecurityDevice), typeof(DbSecurityUser))
                                    .Join <DbSession, DbSecurityDevice>("LEFT", o => o.DeviceKey, o => o.Key)
                                    .Join <DbSession, DbSecurityApplication>("LEFT", o => o.ApplicationKey, o => o.Key)
                                    .Join <DbSession, DbSecurityUser>("LEFT", o => o.UserKey, o => o.Key)
                                    .Where <DbSession>(o => o.Key == sessionId);

                    var sessionData = context.FirstOrDefault <CompositeResult <DbSession, DbSecurityDevice, DbSecurityApplication, DbSecurityUser> >(sql);
                    if (sessionData == null)
                    {
                        throw new KeyNotFoundException($"Session {sessionId} not found");
                    }
                    else
                    {
                        List <IIdentity> retVal = new List <IIdentity>(4);
                        retVal.Add(new Core.Security.ApplicationIdentity(sessionData.Object1.ApplicationKey, sessionData.Object3.PublicId, false));
                        if (sessionData.Object1.DeviceKey.HasValue)
                        {
                            retVal.Add(new DeviceIdentity(sessionData.Object2.Key, sessionData.Object2.PublicId, false));
                        }
                        if (sessionData.Object1.UserKey.HasValue)
                        {
                            retVal.Add(AdoClaimsIdentity.Create(sessionData.Object4, false));
                        }
                        return(retVal.ToArray());
                    }
                }
            }
            catch (Exception e)
            {
                this.m_traceSource.TraceError("Error getting identities for session {0}", session.Id);
                throw new DataPersistenceException($"Error getting identities for session {BitConverter.ToString(session.Id)}");
            }
        }
 /// <summary>
 /// Gets an un-authenticated identity
 /// </summary>
 public IIdentity GetIdentity(Guid sid)
 {
     try
     {
         using (var dataContext = this.m_configuration.Provider.GetReadonlyConnection())
         {
             dataContext.Open();
             var name = dataContext.FirstOrDefault <DbSecurityUser>(o => o.Key == sid)?.UserName;
             if (name == null)
             {
                 return(null);
             }
             else
             {
                 return(AdoClaimsIdentity.Create(name));
             }
         }
     }
     catch
     {
         return(null);
     }
 }
Example #7
0
        /// <summary>
        /// Authenticate using a refresh token
        /// </summary>
        public IPrincipal Authenticate(byte[] refreshToken)
        {
            if (refreshToken == null)
            {
                throw new ArgumentNullException(nameof(refreshToken));
            }
            else if (refreshToken.Length != 32)
            {
                throw new ArgumentOutOfRangeException(nameof(refreshToken), "Invalid refresh token");
            }


            string trokenName = BitConverter.ToString(refreshToken).Replace("-", "");
            var    evt        = new AuthenticatingEventArgs(trokenName);

            this.Authenticating?.Invoke(this, evt);
            if (evt.Cancel)
            {
                throw new SecurityException("Authentication cancelled");
            }

            try
            {
                var principal = AdoClaimsIdentity.Create(refreshToken).CreateClaimsPrincipal();
                this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(trokenName, principal, true));
                return(principal);
            }
            catch (SecurityException e)
            {
                this.m_traceSource.TraceEvent(TraceEventType.Verbose, e.HResult, "Invalid credentials : {0}", refreshToken);

                this.m_traceSource.TraceEvent(TraceEventType.Error, e.HResult, e.ToString());
                this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(trokenName, null, false));
                throw;
            }
        }
        /// <summary>
        /// Authenticate the user using a TwoFactorAuthentication secret
        /// </summary>
        public IPrincipal Authenticate(string userName, string password, string tfaSecret)
        {
            // First, let's verify the TFA
            if (String.IsNullOrEmpty(userName))
            {
                throw new ArgumentNullException(nameof(userName));
            }
            else if (String.IsNullOrEmpty(tfaSecret))
            {
                throw new ArgumentNullException(nameof(tfaSecret));
            }

            // Authentication event args
            var evt = new AuthenticatingEventArgs(userName);

            this.Authenticating?.Invoke(this, evt);
            if (evt.Cancel)
            {
                throw new SecurityException("Authentication cancelled");
            }

            // Password hasher
            var hashingService = ApplicationServiceContext.Current.GetService <IPasswordHashingService>();

            tfaSecret = hashingService.ComputeHash(tfaSecret);

            // Try to authenticate
            try
            {
                using (var dataContext = this.m_configuration.Provider.GetWriteConnection())
                {
                    dataContext.Open();

                    using (var tx = dataContext.BeginTransaction())
                    {
                        try
                        {
                            var user = dataContext.FirstOrDefault <DbSecurityUser>(o => o.UserName.ToLower() == userName.ToLower());
                            if (user == null)
                            {
                                throw new KeyNotFoundException(userName);
                            }

                            var         claims     = dataContext.Query <DbUserClaim>(o => o.SourceKey == user.Key && (o.ClaimType == SanteDBClaimTypes.SanteDBOTAuthCode || o.ClaimType == SanteDBClaimTypes.SanteDBCodeAuth) && (!o.ClaimExpiry.HasValue || o.ClaimExpiry > DateTime.Now));
                            DbUserClaim tfaClaim   = claims.FirstOrDefault(o => o.ClaimType == SanteDBClaimTypes.SanteDBOTAuthCode),
                                        noPassword = claims.FirstOrDefault(o => o.ClaimType == SanteDBClaimTypes.SanteDBCodeAuth);

                            if (tfaClaim == null)
                            {
                                throw new InvalidOperationException("Cannot find appropriate claims for TFA");
                            }

                            // Expiry check
                            IClaimsPrincipal retVal = null;
                            if (String.IsNullOrEmpty(password) &&
                                Boolean.Parse(noPassword?.ClaimValue ?? "false") &&
                                tfaSecret == tfaClaim.ClaimValue) // TFA password hash sent as password, this is a password reset token - It will be set to expire ASAP
                            {
                                retVal = AdoClaimsIdentity.Create(user, true, "Tfa+LastPasswordHash").CreateClaimsPrincipal();
                                (retVal.Identity as IClaimsIdentity).AddClaim(new SanteDBClaim(SanteDBClaimTypes.PurposeOfUse, PurposeOfUseKeys.SecurityAdmin.ToString()));
                                (retVal.Identity as IClaimsIdentity).AddClaim(new SanteDBClaim(SanteDBClaimTypes.SanteDBScopeClaim, PermissionPolicyIdentifiers.LoginPasswordOnly));
                                (retVal.Identity as IClaimsIdentity).AddClaim(new SanteDBClaim(SanteDBClaimTypes.SanteDBScopeClaim, PermissionPolicyIdentifiers.ReadMetadata));
                                (retVal.Identity as IClaimsIdentity).RemoveClaim(retVal.FindFirst(SanteDBClaimTypes.Expiration));
                                (retVal.Identity as IClaimsIdentity).AddClaim(new SanteDBClaim(SanteDBClaimTypes.Expiration, DateTime.Now.AddMinutes(5).ToString("o"))); // Special case, passwoird
                            }
                            else if (!String.IsNullOrEmpty(password))
                            {
                                if (!user.TwoFactorEnabled || tfaSecret == tfaClaim.ClaimValue)
                                {
                                    retVal = this.Authenticate(userName, password) as IClaimsPrincipal;
                                }
                                else
                                {
                                    throw new AuthenticationException("TFA_MISMATCH");
                                }
                            }
                            else
                            {
                                throw new PolicyViolationException(new GenericPrincipal(new GenericIdentity(userName), new string[0]), PermissionPolicyIdentifiers.Login, PolicyGrantType.Deny);
                            }

                            // Now we want to fire authenticated
                            this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(userName, retVal, true));

                            tx.Commit();
                            return(retVal);
                        }
                        catch
                        {
                            tx.Rollback();
                            throw;
                        }
                    }
                }
            }
            catch (Exception e)
            {
                this.m_traceSource.TraceEvent(EventLevel.Verbose, "Invalid credentials : {0}/{1}", userName, password);
                this.m_traceSource.TraceEvent(EventLevel.Error, e.ToString());
                this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(userName, null, false));
                throw;
            }
        }
 /// <summary>
 /// Gets an un-authenticated identity
 /// </summary>
 public IIdentity GetIdentity(string userName)
 {
     return(AdoClaimsIdentity.Create(userName));
 }
Example #10
0
        public IIdentity CreateIdentity(string userName, string password, IPrincipal authContext)
        {
            if (String.IsNullOrEmpty(userName))
            {
                throw new ArgumentNullException(nameof(userName));
            }
            else if (String.IsNullOrEmpty(password))
            {
                throw new ArgumentNullException(nameof(password));
            }
            else if (authContext == null)
            {
                throw new ArgumentNullException(nameof(authContext));
            }

            this.m_traceSource.TraceInformation("Creating identity {0} ({1})", userName, authContext);

            try
            {
                using (var dataContext = this.m_configuration.Provider.GetWriteConnection())
                {
                    dataContext.Open();

                    using (var tx = dataContext.BeginTransaction())
                        try
                        {
                            var hashingService = ApplicationContext.Current.GetService <IPasswordHashingService>();
                            var pdpService     = ApplicationContext.Current.GetService <IPolicyDecisionService>();

                            // Demand create identity
                            new PolicyPermission(System.Security.Permissions.PermissionState.Unrestricted, PermissionPolicyIdentifiers.CreateIdentity, authContext).Demand();

                            // Does this principal have the ability to
                            DbSecurityUser newIdentityUser = new DbSecurityUser()
                            {
                                UserName     = userName,
                                PasswordHash = hashingService.EncodePassword(password),
                                SecurityHash = Guid.NewGuid().ToString(),
                                UserClass    = UserClassKeys.HumanUser
                            };
                            if (authContext != null)
                            {
                                newIdentityUser.CreatedByKey = authContext.GetUserKey(dataContext).Value;
                            }

                            dataContext.Insert(newIdentityUser);
                            var retVal = AdoClaimsIdentity.Create(newIdentityUser);
                            tx.Commit();
                            return(retVal);
                        }
                        catch
                        {
                            tx.Rollback();
                            throw;
                        }
                }
            }
            catch (Exception e)
            {
                this.m_traceSource.TraceEvent(TraceEventType.Error, e.HResult, e.ToString());
                throw;
            }
        }
Example #11
0
        /// <summary>
        /// Authenticate the user using a TwoFactorAuthentication secret
        /// </summary>
        public IPrincipal Authenticate(string userName, string password, string tfaSecret)
        {
            // First, let's verify the TFA
            if (String.IsNullOrEmpty(userName))
            {
                throw new ArgumentNullException(nameof(userName));
            }
            else if (String.IsNullOrEmpty(tfaSecret))
            {
                throw new ArgumentNullException(nameof(tfaSecret));
            }

            // Authentication event args
            var evt = new AuthenticatingEventArgs(userName);

            this.Authenticating?.Invoke(this, evt);
            if (evt.Cancel)
            {
                throw new SecurityException("Authentication cancelled");
            }

            // Password hasher
            var hashingService = ApplicationContext.Current.GetService <IPasswordHashingService>();

            tfaSecret = hashingService.EncodePassword(tfaSecret);

            // Try to authenticate
            try
            {
                using (var dataContext = this.m_configuration.Provider.GetWriteConnection())
                {
                    dataContext.Open();

                    using (var tx = dataContext.BeginTransaction())
                    {
                        try
                        {
                            var user = dataContext.FirstOrDefault <DbSecurityUser>(o => o.UserName.ToLower() == userName.ToLower());
                            if (user == null)
                            {
                                throw new KeyNotFoundException(userName);
                            }

                            var         claims     = dataContext.Query <DbUserClaim>(o => o.SourceKey == user.Key && (o.ClaimType == OpenIzClaimTypes.OpenIZTfaSecretClaim || o.ClaimType == OpenIzClaimTypes.OpenIZTfaSecretExpiry || o.ClaimType == OpenIzClaimTypes.OpenIZPasswordlessAuth));
                            DbUserClaim tfaClaim   = claims.FirstOrDefault(o => o.ClaimType == OpenIzClaimTypes.OpenIZTfaSecretClaim),
                                        tfaExpiry  = claims.FirstOrDefault(o => o.ClaimType == OpenIzClaimTypes.OpenIZTfaSecretExpiry),
                                        noPassword = claims.FirstOrDefault(o => o.ClaimType == OpenIzClaimTypes.OpenIZPasswordlessAuth);

                            if (tfaClaim == null || tfaExpiry == null)
                            {
                                throw new InvalidOperationException("Cannot find appropriate claims for TFA");
                            }

                            // Expiry check
                            ClaimsPrincipal retVal     = null;
                            DateTime        expiryDate = DateTime.Parse(tfaExpiry.ClaimValue);
                            if (expiryDate < DateTime.Now)
                            {
                                throw new SecurityException("TFA secret expired");
                            }
                            else if (String.IsNullOrEmpty(password) &&
                                     Boolean.Parse(noPassword?.ClaimValue ?? "false") &&
                                     tfaSecret == tfaClaim.ClaimValue) // Last known password hash sent as password, this is a password reset token - It will be set to expire ASAP
                            {
                                retVal = AdoClaimsIdentity.Create(user, true, "Tfa+LastPasswordHash").CreateClaimsPrincipal();
                                (retVal.Identity as ClaimsIdentity).AddClaim(new Claim(OpenIzClaimTypes.OpenIzGrantedPolicyClaim, PermissionPolicyIdentifiers.ChangePassword));
                                (retVal.Identity as ClaimsIdentity).RemoveClaim(retVal.FindFirst(ClaimTypes.Expiration));
                                // TODO: Add to configuration
                                (retVal.Identity as ClaimsIdentity).AddClaim(new Claim(ClaimTypes.Expiration, DateTime.Now.AddMinutes(5).ToString("o")));
                            }
                            else if (!String.IsNullOrEmpty(password))
                            {
                                retVal = this.Authenticate(userName, password) as ClaimsPrincipal;
                            }
                            else
                            {
                                throw new PolicyViolationException(PermissionPolicyIdentifiers.Login, PolicyDecisionOutcomeType.Deny);
                            }

                            // Now we want to fire authenticated
                            this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(userName, retVal, true));

                            tx.Commit();
                            return(retVal);
                        }
                        catch
                        {
                            tx.Rollback();
                            throw;
                        }
                    }
                }
            }
            catch (Exception e)
            {
                this.m_traceSource.TraceEvent(TraceEventType.Verbose, e.HResult, "Invalid credentials : {0}/{1}", userName, password);
                this.m_traceSource.TraceEvent(TraceEventType.Error, e.HResult, e.ToString());
                this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(userName, null, false));
                throw;
            }
        }
        /// <summary>
        /// Authenticate this user based on the session
        /// </summary>
        /// <param name="session">The session for which authentication is being saught</param>
        /// <returns>The authenticated principal</returns>
        public IPrincipal Authenticate(ISession session)
        {
            try
            {
                var sessionId = new Guid(session.Id.Take(16).ToArray());

                var adhocCache = ApplicationServiceContext.Current.GetService <IAdhocCacheService>();
                var authCache  = adhocCache?.Get <object[]>($"s{sessionId}");
                using (var context = this.m_configuration.Provider.GetReadonlyConnection())
                {
                    context.Open();

                    var sql = context.CreateSqlStatement <DbSession>().SelectFrom(typeof(DbSession), typeof(DbSecurityApplication), typeof(DbSecurityUser), typeof(DbSecurityDevice))
                              .InnerJoin <DbSecurityApplication>(o => o.ApplicationKey, o => o.Key)
                              .Join <DbSession, DbSecurityUser>("LEFT", o => o.UserKey, o => o.Key)
                              .Join <DbSession, DbSecurityDevice>("LEFT", o => o.DeviceKey, o => o.Key)
                              .Where <DbSession>(s => s.Key == sessionId);


                    DbSecurityApplication securityApplication = null;
                    DbSecurityDevice      securityDevice      = null;
                    DbSecurityUser        securityUser        = null;
                    DbSession             securitySession     = null;

                    if (authCache == null)
                    {
                        var auth = context.FirstOrDefault <CompositeResult <DbSession, DbSecurityApplication, DbSecurityUser, DbSecurityDevice> >(sql);
                        auth.Object2.Secret       = null;
                        auth.Object3.Password     = null;
                        auth.Object3.SecurityHash = null;
                        auth.Object4.DeviceSecret = null;
                        adhocCache?.Add($"s{sessionId}", auth.Values, new TimeSpan(0, 5, 0));
                        securityApplication = auth.Object2;
                        securityDevice      = auth.Object4;
                        securityUser        = auth.Object3;
                        securitySession     = auth.Object1;
                    }
                    else
                    {
                        securitySession = authCache[0] as DbSession ??
                                          (authCache[0] as JObject).ToObject <DbSession>();
                        securityApplication = authCache[1] as DbSecurityApplication ??
                                              (authCache[1] as JObject).ToObject <DbSecurityApplication>();
                        securityUser = authCache[2] as DbSecurityUser ??
                                       (authCache[2] as JObject).ToObject <DbSecurityUser>();
                        securityDevice = authCache[3] as DbSecurityDevice ??
                                         (authCache[3] as JObject).ToObject <DbSecurityDevice>();
                    }

                    // Identities
                    List <IClaimsIdentity> identities = new List <IClaimsIdentity>(3);
                    if (securitySession == null)
                    {
                        throw new SecuritySessionException(SessionExceptionType.NotEstablished, "Session not found", null);
                    }
                    if (securitySession.NotAfter < DateTimeOffset.Now)
                    {
                        throw new SecuritySessionException(SessionExceptionType.Expired, "Session expired", null);
                    }
                    else if (securitySession.NotBefore > DateTimeOffset.Now)
                    {
                        throw new SecuritySessionException(SessionExceptionType.NotYetValid, "Session not yet valid", null);
                    }

                    if (securitySession.ApplicationKey != null)
                    {
                        identities.Add(new Server.Core.Security.ApplicationIdentity(securityApplication.Key, securityApplication.PublicId, true));
                    }
                    if (securitySession.DeviceKey.HasValue)
                    {
                        identities.Add(new DeviceIdentity(securityDevice.Key, securityDevice.PublicId, true));
                    }

                    var principal = securitySession.UserKey.GetValueOrDefault() == Guid.Empty ?
                                    new SanteDBClaimsPrincipal(identities) : AdoClaimsIdentity.Create(context, securityUser, true, "SESSION").CreateClaimsPrincipal(identities);

                    identities.First().AddClaim(new SanteDBClaim(SanteDBClaimTypes.AuthenticationInstant, securitySession.NotBefore.ToString("o")));
                    identities.First().AddClaim(new SanteDBClaim(SanteDBClaimTypes.Expiration, securitySession.NotAfter.ToString("o")));
                    identities.First().AddClaim(new SanteDBClaim(SanteDBClaimTypes.SanteDBSessionIdClaim, securitySession.Key.ToString()));

                    // Add claims from session
                    foreach (var clm in session.Claims.Where(o => o.Type != SanteDBClaimTypes.Sid && o.Type != SanteDBClaimTypes.SanteDBApplicationIdentifierClaim && o.Type != SanteDBClaimTypes.SanteDBDeviceIdentifierClaim))
                    {
                        identities.First().AddClaim(clm);
                    }

                    // TODO: Load additional claims made about the user on the session
                    return(principal);
                }
            }
            catch (Exception e)
            {
                this.m_traceSource.TraceEvent(EventLevel.Verbose, "Invalid session auth: {0}", e.Message);
                this.m_traceSource.TraceEvent(EventLevel.Error, e.ToString());
                this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(null, null, false));
                throw new SecuritySessionException(SessionExceptionType.Other, $"Could not perform session authentication", e);
            }
        }
        /// <summary>
        /// Authenticate a user using their challenge response
        /// </summary>
        public IPrincipal Authenticate(string userName, Guid challengeKey, string response, String tfa)
        {
            try
            {
                var authArgs = new AuthenticatingEventArgs(userName);
                this.Authenticating?.Invoke(this, authArgs);
                if (authArgs.Cancel)
                {
                    throw new SecurityException("Authentication cancelled");
                }

                var hashService  = ApplicationServiceContext.Current.GetService <IPasswordHashingService>();
                var responseHash = hashService.ComputeHash(response);

                // Connection to perform auth
                using (var context = this.m_configuration.Provider.GetWriteConnection())
                {
                    context.Open();
                    var query = context.CreateSqlStatement <DbSecurityUser>().SelectFrom(typeof(DbSecurityUser), typeof(DbSecurityUserChallengeAssoc))
                                .InnerJoin <DbSecurityUserChallengeAssoc>(o => o.Key, o => o.UserKey)
                                .Where(o => o.UserName.ToLower() == userName.ToLower() && o.ObsoletionTime == null)
                                .And <DbSecurityUserChallengeAssoc>(o => o.ExpiryTime > DateTimeOffset.Now);
                    var dbUser = context.FirstOrDefault <CompositeResult <DbSecurityUser, DbSecurityUserChallengeAssoc> >(query);

                    // User found?
                    if (dbUser == null)
                    {
                        throw new SecurityException("AUTH_INV");
                    }

                    // TFA?
                    if (!String.IsNullOrEmpty(tfa))
                    {
                        var tfaSecret = ApplicationServiceContext.Current.GetService <IPasswordHashingService>().ComputeHash(tfa);

                        var         claims   = context.Query <DbUserClaim>(o => o.SourceKey == dbUser.Object1.Key && (!o.ClaimExpiry.HasValue || o.ClaimExpiry > DateTimeOffset.Now)).ToList();
                        DbUserClaim tfaClaim = claims.FirstOrDefault(o => o.ClaimType == SanteDBClaimTypes.SanteDBOTAuthCode);
                        if (tfaClaim == null || !tfaSecret.Equals(tfaClaim.ClaimValue, StringComparison.OrdinalIgnoreCase))
                        {
                            throw new SecurityException("TFA_MISMATCH");
                        }
                    }

                    if (dbUser.Object1.Lockout > DateTimeOffset.Now)
                    {
                        throw new SecurityException("AUTH_LCK");
                    }
                    else if (dbUser.Object2.ChallengeResponse != responseHash || dbUser.Object1.Lockout.GetValueOrDefault() > DateTimeOffset.Now) // Increment invalid
                    {
                        dbUser.Object1.InvalidLoginAttempts++;
                        if (dbUser.Object1.InvalidLoginAttempts > this.m_securityConfiguration.GetSecurityPolicy <Int32>(SecurityPolicyIdentification.MaxInvalidLogins, 5))
                        {
                            dbUser.Object1.Lockout = DateTimeOffset.Now.Add(new TimeSpan(0, 0, dbUser.Object1.InvalidLoginAttempts.Value * 30));
                        }
                        dbUser.Object1.UpdatedByKey = Guid.Parse(AuthenticationContext.SystemUserSid);
                        dbUser.Object1.UpdatedTime  = DateTimeOffset.Now;

                        context.Update(dbUser.Object1);
                        if (dbUser.Object1.Lockout > DateTimeOffset.Now)
                        {
                            throw new AuthenticationException("AUTH_LCK");
                        }
                        else
                        {
                            throw new AuthenticationException("AUTH_INV");
                        }
                    }
                    else
                    {
                        var principal = AdoClaimsIdentity.Create(context, dbUser.Object1, true, "Secret=" + challengeKey.ToString()).CreateClaimsPrincipal();

                        this.m_policyEnforcementService.Demand(PermissionPolicyIdentifiers.Login, principal); // must still be allowed to login

                        (principal.Identity as IClaimsIdentity).AddClaim(new SanteDBClaim(SanteDBClaimTypes.PurposeOfUse, PurposeOfUseKeys.SecurityAdmin.ToString()));
                        (principal.Identity as IClaimsIdentity).AddClaim(new SanteDBClaim(SanteDBClaimTypes.SanteDBScopeClaim, PermissionPolicyIdentifiers.ReadMetadata));
                        (principal.Identity as IClaimsIdentity).AddClaim(new SanteDBClaim(SanteDBClaimTypes.SanteDBScopeClaim, PermissionPolicyIdentifiers.LoginPasswordOnly));

                        this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(userName, principal, true));
                        return(principal);
                    }
                }
            }
            catch (Exception e)
            {
                this.m_tracer.TraceError("Challenge authentication failed: {0}", e);
                this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(userName, null, false));
                throw new AuthenticationException($"Challenge authentication failed");
            }
        }