/// <summary>
        /// Authenticate this user with a local PIN number
        /// </summary>
        /// <param name="userName">The name of the user</param>
        /// <param name="password">The password of the user</param>
        /// <param name="pin">The PIN number for PIN based authentication</param>
        /// <returns>The authenticated principal</returns>
        private IPrincipal AuthenticateInternal(String userName, string password, byte[] pin)
        {
            var config = ApplicationContext.Current.Configuration.GetSection <SecurityConfigurationSection>();

            // Pre-event
            AuthenticatingEventArgs e = new AuthenticatingEventArgs(userName)
            {
            };

            this.Authenticating?.Invoke(this, e);
            if (e.Cancel)
            {
                this.m_tracer.TraceWarning("Pre-Event hook indicates cancel {0}", userName);
                return(e.Principal);
            }

            IPrincipal retVal = null;

            try
            {
                // Connect to the db
                var connection = this.CreateConnection();
                using (connection.Lock())
                {
                    // Password service
                    IPasswordHashingService passwordHash = ApplicationContext.Current.GetService(typeof(IPasswordHashingService)) as IPasswordHashingService;

                    DbSecurityUser dbs = connection.Table <DbSecurityUser>().FirstOrDefault(o => o.UserName.ToLower() == userName.ToLower() && !o.ObsoletionTime.HasValue);
                    if (dbs == null)
                    {
                        throw new SecurityException(Strings.locale_invalidUserNamePassword);
                    }
                    else if (dbs.Lockout.HasValue && dbs.Lockout > DateTime.Now)
                    {
                        throw new SecurityException(Strings.locale_accountLocked);
                    }
                    else if (dbs.ObsoletionTime != null)
                    {
                        throw new SecurityException(Strings.locale_accountObsolete);
                    }
                    else if (!String.IsNullOrEmpty(password) && passwordHash.ComputeHash(password) != dbs.Password ||
                             pin != null && passwordHash.ComputeHash(Encoding.UTF8.GetString(pin.Select(o => (byte)(o + 48)).ToArray(), 0, pin.Length)) != dbs.PinHash)
                    {
                        dbs.InvalidLoginAttempts++;
                        connection.Update(dbs);
                        throw new SecurityException(Strings.locale_invalidUserNamePassword);
                    }
                    else if (config?.MaxInvalidLogins.HasValue == true && dbs.InvalidLoginAttempts > config?.MaxInvalidLogins)
                    { //s TODO: Make this configurable
                        dbs.Lockout = DateTime.Now.AddSeconds(30 * (dbs.InvalidLoginAttempts - config.MaxInvalidLogins.Value));
                        connection.Update(dbs);
                        throw new SecurityException(Strings.locale_accountLocked);
                    } // TODO: Lacks login permission
                    else
                    {
                        dbs.LastLoginTime        = DateTime.Now;
                        dbs.InvalidLoginAttempts = 0;
                        connection.Update(dbs);

                        var roles = connection.Query <DbSecurityRole>("SELECT security_role.* FROM security_user_role INNER JOIN security_role ON (security_role.uuid = security_user_role.role_id) WHERE lower(security_user_role.user_id) = lower(?)",
                                                                      dbs.Uuid).Select(o => o.Name).ToArray();

                        var additionalClaims = new List <IClaim>()
                        {
                            new SanteDBClaim(SanteDBClaimTypes.NameIdentifier, dbs.Key.ToString()),
                            new SanteDBClaim(SanteDBClaimTypes.DefaultNameClaimType, dbs.UserName),
                            new SanteDBClaim(SanteDBClaimTypes.SanteDBApplicationIdentifierClaim, ApplicationContext.Current.Application.Key.ToString()), // Local application only allows
                            new SanteDBClaim(SanteDBClaimTypes.SanteDBDeviceIdentifierClaim, ApplicationContext.Current.Device.Key.ToString())
                        };

                        // Create the principal
                        retVal = new SQLitePrincipal(new SQLiteIdentity(dbs.UserName, true, DateTime.Now, DateTime.Now.Add(config?.MaxLocalSession ?? new TimeSpan(0, 15, 0)), additionalClaims), roles);
                        ApplicationServiceContext.Current.GetService <IPolicyEnforcementService>().Demand(PermissionPolicyIdentifiers.Login, retVal);
                    }
                }

                // Post-event
                this.Authenticated?.Invoke(e, new AuthenticatedEventArgs(userName, retVal, true));
            }
            catch (Exception ex)
            {
                this.m_tracer.TraceError("Error establishing session: {0}", ex);
                this.Authenticated?.Invoke(e, new AuthenticatedEventArgs(userName, retVal, false));
                throw new DataPersistenceException($"Error establishing session", ex);
            }

            return(retVal);
        }
예제 #2
0
        /// <summary>
        /// Authenticate the device
        /// </summary>
        public IPrincipal Authenticate(string deviceId, string deviceSecret, AuthenticationMethod authMethod = AuthenticationMethod.Any)
        {
            var config = ApplicationContext.Current.Configuration.GetSection <SecurityConfigurationSection>();

            if (!authMethod.HasFlag(AuthenticationMethod.Local))
            {
                throw new InvalidOperationException("Identity provider only supports local auth");
            }

            // Pre-event
            AuthenticatingEventArgs e = new AuthenticatingEventArgs(deviceId)
            {
            };

            this.Authenticating?.Invoke(this, e);
            if (e.Cancel)
            {
                this.m_tracer.TraceWarning("Pre-Event hook indicates cancel {0}", deviceId);
                return(e.Principal);
            }

            IPrincipal retVal = null;

            try
            {
                // Connect to the db
                var connection = this.CreateConnection();
                using (connection.Lock())
                {
                    // Password service
                    IPasswordHashingService passwordHash = ApplicationContext.Current.GetService(typeof(IPasswordHashingService)) as IPasswordHashingService;

                    DbSecurityDevice dbd = connection.Table <DbSecurityDevice>().FirstOrDefault(o => o.PublicId.ToLower() == deviceId.ToLower());
                    if (dbd == null)
                    {
                        throw new SecurityException(Strings.locale_authenticationFailure);
                    }
                    else if (config?.MaxInvalidLogins.HasValue == true && dbd.Lockout.HasValue && dbd.Lockout > DateTime.Now)
                    {
                        throw new SecurityException(Strings.locale_accountLocked);
                    }
                    else if (dbd.ObsoletionTime != null)
                    {
                        throw new SecurityException(Strings.locale_accountObsolete);
                    }
                    else if (!String.IsNullOrEmpty(deviceSecret) && passwordHash.ComputeHash(deviceSecret) != dbd.DeviceSecret)
                    {
                        dbd.InvalidAuthAttempts++;
                        connection.Update(dbd);
                        throw new SecurityException(Strings.locale_authenticationFailure);
                    }
                    else if (config?.MaxInvalidLogins.HasValue == true && dbd.InvalidAuthAttempts > config?.MaxInvalidLogins)
                    { //s TODO: Make this configurable
                        dbd.Lockout = DateTime.Now.AddSeconds(30 * (dbd.InvalidAuthAttempts - config.MaxInvalidLogins.Value));
                        connection.Update(dbd);
                        throw new SecurityException(Strings.locale_accountLocked);
                    } // TODO: Lacks login permission
                    else
                    {
                        dbd.LastAuthTime        = DateTime.Now;
                        dbd.InvalidAuthAttempts = 0;
                        connection.Update(dbd);

                        IPolicyDecisionService    pdp = ApplicationContext.Current.GetService <IPolicyDecisionService>();
                        IPolicyInformationService pip = ApplicationContext.Current.GetService <IPolicyInformationService>();
                        List <IClaim>             additionalClaims = new List <IClaim>();
                        additionalClaims.AddRange(pip.GetPolicies(dbd).Where(o => o.Rule == PolicyGrantType.Grant).Select(o => new SanteDBClaim(SanteDBClaimTypes.SanteDBGrantedPolicyClaim, o.Policy.Oid)));
                        additionalClaims.Add(new SanteDBClaim(SanteDBClaimTypes.SanteDBDeviceIdentifierClaim, dbd.Key.ToString()));
                        additionalClaims.Add(new SanteDBClaim(SanteDBClaimTypes.SanteDBApplicationIdentifierClaim, ApplicationContext.Current.Application.Key.ToString())); // Local application only for SQLite
                        // Create the principal
                        retVal = new SQLitePrincipal(new SQLiteDeviceIdentity(dbd.PublicId, true, DateTime.Now, DateTime.Now.Add(config?.MaxLocalSession ?? new TimeSpan(0, 15, 0)), additionalClaims), new string[] { });

                        ApplicationServiceContext.Current.GetService <IPolicyEnforcementService>().Demand(PermissionPolicyIdentifiers.LoginAsService, retVal);
                    }
                }

                // Post-event
                this.Authenticated?.Invoke(e, new AuthenticatedEventArgs(deviceId, retVal, true));
            }
            catch (Exception ex)
            {
                this.m_tracer.TraceError("Error establishing device session ({1}): {0}", ex, deviceSecret);
                this.Authenticated?.Invoke(e, new AuthenticatedEventArgs(deviceId, retVal, false));

                throw;
            }

            return(retVal);
        }
        /// <summary>
        /// Authenticates the specified user in the database against the selected challenge and selected response
        /// </summary>
        public IPrincipal Authenticate(string userName, Guid challengeKey, string response, string tfaSecret)
        {
            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
                var conn = this.CreateConnection();
                using (conn.Lock())
                {
                    var query  = "select * from security_user inner join security_user_challenge on (security_user.uuid = security_user_challenge.user_uuid) where lower(security_user.username) = lower(?) and challenge_uuid = ? and security_user.obsoletionTime is null and (security_user_challenge.expiration is null or security_user_challenge.expiration > ?)";
                    var dbUser = conn.Query <DbSecurityUserChallengeAssoc.QueryResult>(query, userName, challengeKey.ToByteArray(), DateTime.Now).FirstOrDefault();

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

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

                        conn.Update(dbUser, typeof(DbSecurityUser));
                        if (dbUser.Lockout > DateTime.Now)
                        {
                            throw new AuthenticationException("AUTH_LCK");
                        }
                        else
                        {
                            throw new AuthenticationException("AUTH_INV");
                        }
                    }
                    else
                    {
                        dbUser.LastLoginTime        = DateTime.Now;
                        dbUser.InvalidLoginAttempts = 0;
                        conn.Update(dbUser, typeof(DbSecurityUser));

                        var roles = conn.Query <DbSecurityRole>("SELECT security_role.* FROM security_user_role INNER JOIN security_role ON (security_role.uuid = security_user_role.role_id) WHERE lower(security_user_role.user_id) = lower(?)",
                                                                dbUser.Uuid).Select(o => o.Name).ToArray();

                        var additionalClaims = new List <IClaim>()
                        {
                            new SanteDBClaim(SanteDBClaimTypes.NameIdentifier, dbUser.Key.ToString()),
                            new SanteDBClaim(SanteDBClaimTypes.DefaultNameClaimType, dbUser.UserName),
                            new SanteDBClaim(SanteDBClaimTypes.SanteDBApplicationIdentifierClaim, ApplicationContext.Current.Application.Key.ToString()), // Local application only allows
                            new SanteDBClaim(SanteDBClaimTypes.SanteDBDeviceIdentifierClaim, ApplicationContext.Current.Device.Key.ToString())
                        };

                        // Create the principal
                        var principal = new SQLitePrincipal(new SQLiteIdentity(dbUser.UserName, true, DateTime.Now, DateTime.Now.Add(this.m_securityConfiguration.MaxLocalSession), additionalClaims), roles);
                        ApplicationServiceContext.Current.GetService <IPolicyEnforcementService>().Demand(PermissionPolicyIdentifiers.Login, principal);

                        new PolicyPermission(System.Security.Permissions.PermissionState.Unrestricted, PermissionPolicyIdentifiers.Login, principal).Demand(); // 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");
            }
        }