/// <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>
        /// 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);
            }
        }
Exemple #3
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 = SqlClaimsIdentity.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
        /// </summary>
        /// <param name="principal">Principal.</param>
        /// <param name="password">Password.</param>
        public IPrincipal Authenticate(IPrincipal principal, String password)
        {
            var config = ApplicationContext.Current.Configuration.GetSection <SecurityConfigurationSection>();

            if (principal == null)
            {
                throw new ArgumentNullException(nameof(principal));
            }
            else if (String.IsNullOrEmpty(password))
            {
                if (principal.Identity.IsAuthenticated)
                {
                    // Refresh
                    if (principal is SQLitePrincipal) /// extend the existing session
                    {
                        (principal as SQLitePrincipal).Expires = DateTime.Now.Add(config?.MaxLocalSession ?? new TimeSpan(0, 15, 0));
                    }
                    else if (principal is ClaimsPrincipal) // switch them to a SQLitePrincipal
                    {
                        var sid   = (principal as ClaimsPrincipal).FindClaim(ClaimTypes.Sid)?.Value;
                        var uname = (principal as ClaimsPrincipal).FindClaim(ClaimsIdentity.DefaultNameClaimType)?.Value;
                        if (!String.IsNullOrEmpty(uname))
                        {
                            ApplicationContext.Current.GetService <ITickleService>()?.SendTickle(new Tickler.Tickle(Guid.Parse(sid), Tickler.TickleType.SecurityInformation | Tickler.TickleType.Toast, Strings.locale_securitySwitchedMode, DateTime.Now.AddSeconds(10)));
                            return(new SQLitePrincipal(new SQLiteIdentity(uname, true), (principal as ClaimsPrincipal).Claims.Where(o => o.Type == ClaimsIdentity.DefaultRoleClaimType).Select(o => o.Value).ToArray())
                            {
                                Expires = DateTime.Now.Add(config?.MaxLocalSession ?? new TimeSpan(0, 15, 0)),
                                IssueTime = DateTime.Now
                            });
                        }
                        else
                        {
                            throw new SecurityException(Strings.locale_sessionError);
                        }
                    }
                    return(principal);
                }
                else
                {
                    throw new ArgumentNullException(nameof(password));
                }
            }

            // Pre-event
            AuthenticatingEventArgs e = new AuthenticatingEventArgs(principal.Identity.Name, password)
            {
                Principal = principal
            };

            this.Authenticating?.Invoke(this, e);
            if (e.Cancel)
            {
                this.m_tracer.TraceWarning("Pre-Event hook indicates cancel {0}", principal.Identity.Name);
                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 == principal.Identity.Name);
                    if (dbs == null)
                    {
                        throw new SecurityException(Strings.locale_invalidUserNamePassword);
                    }
                    else if (config?.MaxInvalidLogins.HasValue == true && 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 (passwordHash.ComputeHash(password) != dbs.PasswordHash)
                    {
                        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);

                        // Create the principal
                        retVal = new SQLitePrincipal(new SQLiteIdentity(dbs.UserName, true),
                                                     connection.Query <DbSecurityRole>("SELECT security_role.* FROM security_user_role INNER JOIN security_role ON (security_role.uuid = security_user_role.role_id) WHERE security_user_role.user_id = ?",
                                                                                       dbs.Uuid).Select(o => o.Name).ToArray())
                        {
                            IssueTime = DateTime.Now,
                            Expires   = DateTime.Now.Add(config?.MaxLocalSession ?? new TimeSpan(0, 15, 0))
                        };
                    }
                }

                // Post-event
                this.Authenticated?.Invoke(e, new AuthenticatedEventArgs(principal.Identity.Name, password, true)
                {
                    Principal = retVal
                });
            }
            catch (Exception ex)
            {
                this.m_tracer.TraceError("Error establishing session: {0}", ex);
                this.Authenticated?.Invoke(e, new AuthenticatedEventArgs(principal.Identity.Name, password, false)
                {
                    Principal = retVal
                });

                throw;
            }

            return(retVal);
        }
        /// <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>
        /// 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);
        }
        /// <summary>
        /// Authenticate the user
        /// </summary>
        /// <param name="principal">Principal.</param>
        /// <param name="password">Password.</param>
        public System.Security.Principal.IPrincipal Authenticate(System.Security.Principal.IPrincipal principal, string password, String tfaSecret)
        {
            AuthenticatingEventArgs e = new AuthenticatingEventArgs(principal.Identity.Name, password)
            {
                Principal = principal
            };

            this.Authenticating?.Invoke(this, e);
            if (e.Cancel)
            {
                this.m_tracer.TraceWarning("Pre-Event ordered cancel of auth {0}", principal);
                return(e.Principal);
            }

            var localIdp = new LocalIdentityService();

            // Get the scope being requested
            String scope = "*";

            if (principal is ClaimsPrincipal)
            {
                scope = (principal as ClaimsPrincipal).Claims.FirstOrDefault(o => o.Type == ClaimTypes.OpenIzScopeClaim)?.Value ?? scope;
            }
            else if (principal is SQLitePrincipal && password == null)
            {
                return(localIdp.Authenticate(principal, password));
            }
            else
            {
                scope = ApplicationContext.Current.GetRestClient("imsi")?.Description.Endpoint[0].Address ??
                        ApplicationContext.Current.GetRestClient("ami")?.Description.Endpoint[0].Address ??
                        "*";
            }

            // Authenticate
            IPrincipal retVal = null;

            try
            {
                using (IRestClient restClient = ApplicationContext.Current.GetRestClient("acs"))
                {
                    try
                    {
                        // Set credentials
                        restClient.Credentials = new OAuthTokenServiceCredentials(principal);

                        // Create grant information
                        OAuthTokenRequest request = null;
                        if (!String.IsNullOrEmpty(password))
                        {
                            request = new OAuthTokenRequest(principal.Identity.Name, password, scope);
                        }
                        else if (principal is TokenClaimsPrincipal)
                        {
                            request = new OAuthTokenRequest(principal as TokenClaimsPrincipal, scope);
                        }
                        else
                        {
                            request = new OAuthTokenRequest(principal.Identity.Name, null, scope);
                        }

                        try
                        {
                            restClient.Requesting += (o, p) =>
                            {
                                p.AdditionalHeaders.Add("X-OpenIZClient-Claim", Convert.ToBase64String(Encoding.UTF8.GetBytes(String.Format("{0}={1}", ClaimTypes.OpenIzScopeClaim, scope))));
                                if (!String.IsNullOrEmpty(tfaSecret))
                                {
                                    p.AdditionalHeaders.Add("X-OpenIZ-TfaSecret", tfaSecret);
                                }
                            };

                            // Invoke
                            if (ApplicationContext.Current.GetService <INetworkInformationService>().IsNetworkAvailable)
                            {
                                if (principal.Identity.Name == ApplicationContext.Current.Configuration.GetSection <SecurityConfigurationSection>().DeviceName)
                                {
                                    restClient.Description.Endpoint[0].Timeout = restClient.Description.Endpoint[0].Timeout * 2;
                                }
                                else
                                {
                                    restClient.Description.Endpoint[0].Timeout = (int)(restClient.Description.Endpoint[0].Timeout * 0.6666f);
                                }

                                OAuthTokenResponse response = restClient.Post <OAuthTokenRequest, OAuthTokenResponse>("oauth2_token", "application/x-www-urlform-encoded", request);
                                retVal = new TokenClaimsPrincipal(response.AccessToken, response.TokenType, response.RefreshToken);
                            }
                            else
                            {
                                this.m_tracer.TraceWarning("Network unavailable, trying local");
                                try
                                {
                                    retVal = localIdp.Authenticate(principal, password);
                                }
                                catch (Exception ex2)
                                {
                                    this.m_tracer.TraceError("Error falling back to local IDP: {0}", ex2);
                                    throw new SecurityException(String.Format(Strings.err_offline_use_cache_creds, ex2.Message), ex2);
                                }
                            }
                            this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(principal.Identity.Name, password, true)
                            {
                                Principal = retVal
                            });
                        }
                        catch (WebException ex) // Raw level web exception
                        {
                            // Not network related, but a protocol level error
                            if (ex.Status == WebExceptionStatus.ProtocolError)
                            {
                                throw;
                            }

                            this.m_tracer.TraceWarning("Original OAuth2 request failed trying local. {0}", ex.Message);
                            try
                            {
                                retVal = localIdp.Authenticate(principal, password);
                            }
                            catch (Exception ex2)
                            {
                                this.m_tracer.TraceError("Error falling back to local IDP: {0}", ex2);
                                throw new SecurityException(String.Format(Strings.err_offline_use_cache_creds, ex2.Message), ex2);
                            }
                        }
                        catch (SecurityException ex)
                        {
                            this.m_tracer.TraceError("Server was contacted however the token is invalid: {0}", ex.Message);
                            throw;
                        }
                        catch (Exception ex) // fallback to local
                        {
                            try
                            {
                                this.m_tracer.TraceWarning("Original OAuth2 request failed trying local. {0}", ex.Message);
                                retVal = localIdp.Authenticate(principal, password);
                            }
                            catch (Exception ex2)
                            {
                                this.m_tracer.TraceError("Error falling back to local IDP: {0}", ex2);

                                throw new SecurityException(String.Format(Strings.err_offline_use_cache_creds, ex2.Message), ex2);
                            }
                        }


                        // We have a match! Lets make sure we cache this data
                        // TODO: Clean this up
                        try
                        {
                            if (!(retVal is SQLitePrincipal))
                            {
                                ApplicationContext.Current.GetService <IThreadPoolService>().QueueUserWorkItem(o => this.SynchronizeSecurity(password, o as IPrincipal), retVal);
                            }
                        }
                        catch (Exception ex)
                        {
                            try
                            {
                                this.m_tracer.TraceWarning("Failed to fetch remote security parameters - {0}", ex.Message);
                                retVal = localIdp.Authenticate(principal, password);
                            }
                            catch (Exception ex2)
                            {
                                this.m_tracer.TraceError("Error falling back to local IDP: {0}", ex2);
                                throw new SecurityException(String.Format(Strings.err_offline_use_cache_creds, ex2.Message));
                            }
                        }
                    }
                    catch (RestClientException <OAuthTokenResponse> ex)
                    {
                        this.m_tracer.TraceError("REST client exception: {0}", ex.Message);
                        var se = new SecurityException(
                            String.Format("err_oauth2_{0}", ex.Result.Error),
                            ex
                            );
                        se.Data.Add("detail", ex.Result);
                        throw se;
                    }
                    catch (SecurityTokenException ex)
                    {
                        this.m_tracer.TraceError("TOKEN exception: {0}", ex.Message);
                        throw new SecurityException(
                                  String.Format("err_token_{0}", ex.Type),
                                  ex
                                  );
                    }
                    catch (SecurityException ex)
                    {
                        this.m_tracer.TraceError("Security exception: {0}", ex.Message);
                        throw;
                    }
                    catch (Exception ex)
                    {
                        this.m_tracer.TraceError("Generic exception: {0}", ex);
                        throw new SecurityException(
                                  Strings.err_authentication_exception,
                                  ex);
                    }
                }
            }
            catch
            {
                this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(principal.Identity.Name, password, false)
                {
                    Principal = retVal
                });
                throw;
            }

            return(retVal);
        }
Exemple #8
0
 public void On_pop3Client_Authenticating(object sender, AuthenticatingEventArgs e)
 {
     log.Debug(String.Format("In autenticazione. Host: {0} - User: {1} - Password: {2}", e.Host, e.Username, e.Password));
 }
        /// <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");
            }
        }
        /// <summary>
        /// Authenticate the user
        /// </summary>
        /// <param name="principal">Principal.</param>
        /// <param name="password">Password.</param>
        public System.Security.Principal.IPrincipal Authenticate(System.Security.Principal.IPrincipal principal, string password, String tfaSecret)
        {
            AuthenticatingEventArgs e = new AuthenticatingEventArgs(principal.Identity.Name);

            this.Authenticating?.Invoke(this, e);
            if (e.Cancel)
            {
                this.m_tracer.TraceWarning("Pre-Event ordered cancel of auth {0}", principal);
                return(null);
            }

            // Get the scope being requested
            String scope = "*";

            // Authenticate
            IPrincipal retVal = null;

            try
            {
                using (IRestClient restClient = ApplicationContext.Current.GetRestClient(Core.Interop.ServiceEndpointType.AuthenticationService))
                {
                    // Set credentials
                    restClient.Credentials = new OAuthTokenServiceCredentials(principal);

                    // Create grant information
                    OAuthTokenRequest request = null;
                    if (!String.IsNullOrEmpty(password))
                    {
                        request = new OAuthTokenRequest(principal.Identity.Name, password, scope);
                    }
                    else if (principal is TokenClaimsPrincipal)
                    {
                        request = new OAuthTokenRequest(principal as TokenClaimsPrincipal, scope);
                    }
                    else
                    {
                        request = new OAuthTokenRequest(principal.Identity.Name, null, scope);
                    }

                    // Set credentials
                    if (restClient.Description.Binding.Security?.Mode == Core.Http.Description.SecurityScheme.Basic)
                    {
                        restClient.Credentials = new OAuthTokenServiceCredentials(principal);
                    }
                    else
                    {
                        request.ClientId     = ApplicationContext.Current.ApplicationName;
                        request.ClientSecret = ApplicationContext.Current.ApplicationSecret;
                    }

                    try
                    {
                        restClient.Requesting += (o, p) =>
                        {
                            if (!String.IsNullOrEmpty(tfaSecret))
                            {
                                p.AdditionalHeaders.Add("X-SanteDB-TfaSecret", tfaSecret);
                            }
                        };

                        OAuthTokenResponse response = restClient.Post <OAuthTokenRequest, OAuthTokenResponse>("oauth2_token", "application/x-www-form-urlencoded", request);
                        retVal = new TokenClaimsPrincipal(response.AccessToken, response.IdToken, response.TokenType, response.RefreshToken);
                        this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(principal.Identity.Name, retVal, true));
                    }
                    catch (RestClientException <OAuthTokenResponse> ex)
                    {
                        this.m_tracer.TraceWarning("OAUTH Server Responded: {0}", ex.Result.ErrorDescription);
                    }
                    catch (WebException ex) // Raw level web exception
                    {
                        this.m_tracer.TraceError("Error authenticating: {0}", ex.Message);
                    }
                    catch (SecurityException ex)
                    {
                        this.m_tracer.TraceError("Server was contacted however the token is invalid: {0}", ex.Message);
                        throw;
                    }
                    catch (Exception ex) // fallback to local
                    {
                        this.m_tracer.TraceError("General Authentication Error: {0}", ex.Message);
                    }
                }
            }
            catch
            {
                this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(principal.Identity.Name, retVal, false));
                throw;
            }

            return(retVal);
        }
Exemple #11
0
        /// <summary>
        /// Challenge key authentication for password change only
        /// </summary>
        public IPrincipal Authenticate(string userName, Guid challengeKey, string response, string tfaSecret)
        {
            AuthenticatingEventArgs e = new AuthenticatingEventArgs(userName);

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

            var        localIdp = ApplicationContext.Current.GetService <IOfflineIdentityProviderService>();
            IPrincipal retVal   = null;

            // Get the scope being requested
            try
            {
                if (localIdp?.IsLocalUser(userName) == true)
                {
                    var localScIdp = ApplicationServiceContext.Current.GetService <IOfflineSecurityChallengeIdentityService>();
                    retVal = localScIdp.Authenticate(userName, challengeKey, response, tfaSecret);
                }
                else
                {
                    using (IRestClient restClient = ApplicationContext.Current.GetRestClient("acs"))
                    {
                        // Create grant information
                        OAuthTokenRequest request = new OAuthResetTokenRequest(userName, challengeKey.ToString(), response);

                        // Set credentials
                        if (ApplicationContext.Current.Configuration.GetSection <SecurityConfigurationSection>().DomainAuthentication == DomainClientAuthentication.Basic)
                        {
                            restClient.Credentials = new OAuthTokenServiceCredentials(null);
                        }
                        else
                        {
                            request.ClientId     = ApplicationContext.Current.Application.Name;
                            request.ClientSecret = ApplicationContext.Current.Application.ApplicationSecret;
                        }

                        try
                        {
                            restClient.Requesting += (o, p) =>
                            {
                                // Add device credential
                                if (!String.IsNullOrEmpty(ApplicationContext.Current.Device.DeviceSecret))
                                {
                                    p.AdditionalHeaders.Add(HeaderTypes.HttpDeviceAuthentication, $"BASIC {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{ApplicationContext.Current.Device.Name}:{ApplicationContext.Current.Device.DeviceSecret}"))}");
                                }

                                if (!String.IsNullOrEmpty(tfaSecret))
                                {
                                    p.AdditionalHeaders.Add(HeaderTypes.HttpTfaSecret, tfaSecret);
                                }
                            };

                            // Invoke
                            if (ApplicationServiceContext.Current.GetService <INetworkInformationService>().IsNetworkAvailable)
                            {
                                restClient.Description.Endpoint[0].Timeout = 5000;
                                restClient.Invoke <Object, Object>("PING", "/", null, null);
                                restClient.Description.Endpoint[0].Timeout = (int)(restClient.Description.Endpoint[0].Timeout * 0.6666f);

                                // Swap out the endpoint and authenticate
                                var configuration = this.GetConfigurationInfo();
                                if (configuration == null) // default action
                                {
                                    var oauthResponse = restClient.Post <OAuthTokenRequest, OAuthTokenResponse>("oauth2_token", "application/x-www-form-urlencoded", request);
                                    retVal = new TokenClaimsPrincipal(oauthResponse.AccessToken, oauthResponse.IdToken ?? oauthResponse.AccessToken, oauthResponse.TokenType, oauthResponse.RefreshToken, configuration);
                                }
                                else
                                {
                                    if (!configuration.GrantTypesSupported.Contains("password"))
                                    {
                                        throw new InvalidOperationException("Password grants not supported by this provider");
                                    }

                                    var oauthResponse = restClient.Post <OAuthTokenRequest, OAuthTokenResponse>(configuration.TokenEndpoint, "application/x-www-form-urlencoded", request);
                                    retVal = new TokenClaimsPrincipal(oauthResponse.AccessToken, oauthResponse.IdToken ?? oauthResponse.AccessToken, oauthResponse.TokenType, oauthResponse.RefreshToken, configuration);
                                }

                                return(retVal);
                            }
                            else
                            {
                                throw new InvalidOperationException("Cannot send reset credential while offline");
                            }
                        }
                        catch (RestClientException <OAuthTokenResponse> ex)
                        {
                            this.m_tracer.TraceError("REST client exception: {0}", ex.Message);
                            var se = new SecurityException(
                                String.Format("err_oauth2_{0}", ex.Result.Error),
                                ex
                                );
                            se.Data.Add("oauth_result", ex.Result);
                            throw se;
                        }
                        catch (SecurityException ex)
                        {
                            this.m_tracer.TraceError("Server was contacted however the token is invalid: {0}", ex.Message);
                            throw;
                        }
                        catch (Exception ex) // fallback to local
                        {
                            throw new SecurityException($"General authentication error occurred: {ex.Message}", ex);
                        }
                    }
                }

                this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(userName, retVal, true));
            }
            catch (Exception ex)
            {
                this.m_tracer.TraceError("OAUTH Error: {0}", ex.ToString());
                this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(userName, null, false));
                throw;
            }

            return(retVal);
        }
Exemple #12
0
        /// <summary>
        /// Do internal authentication
        /// </summary>
        private IPrincipal DoAuthenticationInternal(String userName = null, String password = null, string tfaSecret = null, bool isOverride = false, TokenClaimsPrincipal refreshPrincipal = null, string purposeOfUse = null, string[] policies = null)
        {
            AuthenticatingEventArgs e = new AuthenticatingEventArgs(userName);

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

            var localIdp = ApplicationContext.Current.GetService <IOfflineIdentityProviderService>();

            IPrincipal retVal = null;

            // Authenticate
            try
            {
                // Is the user a LOCAL_USER only?
                if (localIdp?.IsLocalUser(userName) == true)
                {
                    retVal = localIdp.Authenticate(userName, password, tfaSecret);
                }
                else
                {
                    using (IRestClient restClient = ApplicationContext.Current.GetRestClient("acs"))
                    {
                        // Construct oauth req
                        OAuthTokenRequest request = null;
                        if (refreshPrincipal != null)
                        {
                            request = new OAuthTokenRequest(refreshPrincipal, "*");
                        }
                        else if (!String.IsNullOrEmpty(password))
                        {
                            request = new OAuthTokenRequest(userName, password, "*");
                        }
                        else
                        {
                            request = new OAuthTokenRequest(userName, null, "*");
                        }

                        // Explicit policies
                        if (policies != null)
                        {
                            request.Scope = String.Join(" ", policies);
                        }

                        // Set credentials for oauth req
                        if (ApplicationContext.Current.Configuration.GetSection <SecurityConfigurationSection>().DomainAuthentication == DomainClientAuthentication.Basic)
                        {
                            restClient.Credentials = new OAuthTokenServiceCredentials(null);
                        }
                        else
                        {
                            request.ClientId     = ApplicationContext.Current.Application.Name;
                            request.ClientSecret = ApplicationContext.Current.Application.ApplicationSecret;
                        }

                        restClient.Requesting += (o, p) =>
                        {
                            if (!String.IsNullOrEmpty(tfaSecret))
                            {
                                p.AdditionalHeaders.Add(HeaderTypes.HttpTfaSecret, tfaSecret);
                            }
                            if (isOverride)
                            {
                                p.AdditionalHeaders.Add(HeaderTypes.HttpClaims, Convert.ToBase64String(Encoding.UTF8.GetBytes(
                                                                                                           $"{SanteDBClaimTypes.PurposeOfUse}={purposeOfUse};{SanteDBClaimTypes.SanteDBOverrideClaim}=true"
                                                                                                           )));
                            }
                            // Add device credential
                            if (!String.IsNullOrEmpty(ApplicationContext.Current.Device.DeviceSecret))
                            {
                                p.AdditionalHeaders.Add(HeaderTypes.HttpDeviceAuthentication, $"BASIC {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{ApplicationContext.Current.Device.Name}:{ApplicationContext.Current.Device.DeviceSecret}"))}");
                            }
                        };

                        if (ApplicationServiceContext.Current.GetService <INetworkInformationService>().IsNetworkAvailable)
                        {
                            // Try OAUTH server
                            try
                            {
                                restClient.Description.Endpoint[0].Timeout = 5000;
                                restClient.Invoke <Object, Object>("PING", "/", null, null);

                                if (userName == ApplicationContext.Current.Configuration.GetSection <SecurityConfigurationSection>().DeviceName)
                                {
                                    restClient.Description.Endpoint[0].Timeout = restClient.Description.Endpoint[0].Timeout * 2;
                                }
                                else
                                {
                                    restClient.Description.Endpoint[0].Timeout = (int)(restClient.Description.Endpoint[0].Timeout * 0.6666f);
                                }

                                // GEt configuration
                                var configuration = this.GetConfigurationInfo();
                                if (configuration == null) // default action
                                {
                                    var oauthResponse = restClient.Post <OAuthTokenRequest, OAuthTokenResponse>("oauth2_token", "application/x-www-form-urlencoded", request);
                                    retVal = new TokenClaimsPrincipal(oauthResponse.AccessToken, oauthResponse.IdToken ?? oauthResponse.AccessToken, oauthResponse.TokenType, oauthResponse.RefreshToken, configuration);
                                }
                                else
                                {
                                    if (!configuration.GrantTypesSupported.Contains("password"))
                                    {
                                        throw new InvalidOperationException("Password grants not supported by this provider");
                                    }

                                    var oauthResponse = restClient.Post <OAuthTokenRequest, OAuthTokenResponse>(configuration.TokenEndpoint, "application/x-www-form-urlencoded", request);
                                    retVal = new TokenClaimsPrincipal(oauthResponse.AccessToken, oauthResponse.IdToken ?? oauthResponse.AccessToken, oauthResponse.TokenType, oauthResponse.RefreshToken, configuration);
                                }
                            }
                            catch (RestClientException <OAuthTokenResponse> ex) // there was an actual OAUTH problem
                            {
                                this.m_tracer.TraceError("REST client exception: {0}", ex.Message);
                                throw new SecurityException($"err_oauth_{ex.Result.Error}", ex);
                            }
                            catch (SecurityException ex)
                            {
                                this.m_tracer.TraceError("Server was contacted however the token is invalid: {0}", ex.Message);
                                throw;
                            }
                            catch (Exception ex) // All others, try local
                            {
                                this.m_tracer.TraceWarning("Original OAuth2 request failed trying local - Original Exception : {0}", ex);
                            }

                            if (retVal == null) // Some error occurred, use local
                            {
                                this.m_tracer.TraceWarning("Network unavailable, trying local");
                                try
                                {
                                    if (localIdp == null)
                                    {
                                        throw new SecurityException(Strings.err_offline_no_local_available);
                                    }

                                    retVal = localIdp.Authenticate(userName, password);
                                }
                                catch (Exception ex2)
                                {
                                    this.m_tracer.TraceError("Error falling back to local IDP: {0}", ex2);
                                    throw new SecurityException(String.Format(Strings.err_offline_use_cache_creds, ex2.Message), ex2);
                                }
                            }

                            // We have a match! Lets make sure we cache this data
                            // TODO: Clean this up
                            if (!(retVal is IOfflinePrincipal))
                            {
                                try
                                {
                                    ApplicationContext.Current.GetService <IThreadPoolService>().QueueUserWorkItem(o => this.SynchronizeSecurity(password, o as IPrincipal), retVal);
                                }
                                catch (Exception e2)
                                {
                                    this.m_tracer.TraceError("An error occurred when inserting the local credential: {0}", e2);
                                }
                            }
                        }
                        else
                        {
                            retVal = localIdp?.Authenticate(userName, password);
                        }
                    }
                }

                this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(userName, retVal, true));
            }
            catch (Exception ex)
            {
                this.m_tracer.TraceError("OAUTH Error: {0}", ex.ToString());
                this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(userName, retVal, false));
                throw new SecurityException($"Error establishing authentication session - {ex.Message}", ex);
            }

            return(retVal);
        }
        /// <summary>
        /// Authenticate the specified device
        /// </summary>
        public IPrincipal Authenticate(string deviceId, string deviceSecret, AuthenticationMethod authMethod = AuthenticationMethod.Any)
        {
            AuthenticatingEventArgs e = new AuthenticatingEventArgs(deviceId);

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

            // Authenticate
            IPrincipal retVal = null;

            using (IRestClient restClient = ApplicationContext.Current.GetRestClient("acs"))
            {
                // Create grant information
                OAuthTokenRequest request = OAuthTokenRequest.CreateClientCredentialRequest(ApplicationContext.Current.Application.Name, ApplicationContext.Current.Application.ApplicationSecret, "*");

                try
                {
                    restClient.Requesting += (o, p) =>
                    {
                        // Add device credential
                        p.AdditionalHeaders.Add("X-Device-Authorization", $"BASIC {Convert.ToBase64String(Encoding.UTF8.GetBytes($"{deviceId}:{deviceSecret}"))}");
                    };

                    // Invoke
                    if (authMethod.HasFlag(AuthenticationMethod.Online) &&
                        ApplicationServiceContext.Current.GetService <INetworkInformationService>().IsNetworkAvailable&&
                        ApplicationServiceContext.Current.GetService <IAdministrationIntegrationService>()?.IsAvailable() != false) // Network may be on but internet is not available
                    {
                        restClient.Description.Endpoint[0].Timeout = (int)(restClient.Description.Endpoint[0].Timeout * 0.333f);

                        var configuration = this.GetConfigurationInfo();
                        if (configuration == null)
                        {
                            OAuthTokenResponse response = restClient.Post <OAuthTokenRequest, OAuthTokenResponse>("oauth2_token", "application/x-www-form-urlencoded", request);
                            retVal = new TokenClaimsPrincipal(response.AccessToken, response.IdToken ?? response.AccessToken, response.TokenType, response.RefreshToken, configuration);
                        }
                        else
                        {
                            OAuthTokenResponse response = restClient.Post <OAuthTokenRequest, OAuthTokenResponse>(configuration.TokenEndpoint, "application/x-www-form-urlencoded", request);
                            retVal = new TokenClaimsPrincipal(response.AccessToken, response.IdToken ?? response.AccessToken, response.TokenType, response.RefreshToken, configuration);
                        }

                        // HACK: Set preferred sid to device SID
                        var cprincipal = retVal as IClaimsPrincipal;

                        var devId = cprincipal.FindFirst(SanteDBClaimTypes.SanteDBDeviceIdentifierClaim);
                        if (devId != null)
                        {
                            cprincipal.Identities.First().RemoveClaim(cprincipal.FindFirst(SanteDBClaimTypes.Sid));
                            cprincipal.Identities.First().AddClaim(new SanteDBClaim(SanteDBClaimTypes.Sid, devId.Value));
                        }

                        // Synchronize the security devices
                        try
                        {
                            this.SynchronizeSecurity(deviceSecret, deviceId, retVal);
                        }
                        catch (Exception e2)
                        {
                            this.m_tracer.TraceError("Error synchronizing the local device security: {0}", e2);
                        }
                        this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(deviceId, retVal, true)
                        {
                            Principal = retVal
                        });
                    }
                    else if (authMethod.HasFlag(AuthenticationMethod.Local))
                    {
                        // Is this another device authenticating against me?
                        return(ApplicationContext.Current.GetService <IOfflineDeviceIdentityProviderService>().Authenticate(deviceId, deviceSecret));
                    }
                    else
                    {
                        throw new InvalidOperationException("Cannot determine authentication method");
                    }
                }

                catch (SecurityTokenException ex)
                {
                    this.m_tracer.TraceError("TOKEN exception: {0}", ex.Message);
                    throw new SecurityException(
                              String.Format("err_token_{0}", ex.Type),
                              ex
                              );
                }
                catch (SecurityException ex)
                {
                    this.m_tracer.TraceError("Server was contacted however the token is invalid: {0}", ex.Message);
                    throw;
                }
                catch (RestClientException <OAuthTokenResponse> ex)
                {
                    this.m_tracer.TraceError("REST client exception: {0}", ex.Message);

                    // Not network related, but a protocol level error
                    if (authMethod.HasFlag(AuthenticationMethod.Local) && deviceId != ApplicationContext.Current.Device.Name)
                    {
                        this.m_tracer.TraceWarning("Network unavailable falling back to local");
                        return(ApplicationContext.Current.GetService <IOfflineDeviceIdentityProviderService>().Authenticate(deviceId, deviceSecret));
                    }
                    else
                    {
                        this.m_tracer.TraceWarning("Original OAuth2 request failed: {0}", ex.Message);
                        throw new SecurityException(Strings.err_authentication_exception, ex);
                    }
                }
                catch (Exception ex) // Raw level web exception
                {
                    // Not network related, but a protocol level error
                    if (authMethod.HasFlag(AuthenticationMethod.Local) && deviceId != ApplicationContext.Current.Device.Name)
                    {
                        this.m_tracer.TraceWarning("Network unavailable falling back to local");
                        return(ApplicationContext.Current.GetService <IOfflineDeviceIdentityProviderService>().Authenticate(deviceId, deviceSecret));
                    }
                    else
                    {
                        this.m_tracer.TraceWarning("Original OAuth2 request failed: {0}", ex.Message);
                        throw new SecurityException(Strings.err_authentication_exception, ex);
                    }
                }

                return(retVal);
            }
        }
Exemple #14
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);
        }
Exemple #15
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 = new ModelDataContext(this.m_configuration.ReadWriteConnectionString))
                {
                    var user = dataContext.SecurityUsers.FirstOrDefault(o => o.UserName == userName);
                    if (user == null)
                    {
                        throw new KeyNotFoundException(userName);
                    }
                    SecurityUserClaim tfaClaim   = dataContext.SecurityUserClaims.FirstOrDefault(o => o.UserId == user.Id && o.ClaimType == OpenIzClaimTypes.OpenIZTfaSecretClaim),
                                      tfaExpiry  = dataContext.SecurityUserClaims.FirstOrDefault(o => o.UserId == user.Id && o.ClaimType == OpenIzClaimTypes.OpenIZTfaSecretExpiry),
                                      noPassword = dataContext.SecurityUserClaims.FirstOrDefault(o => o.UserId == user.Id && 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 = SqlClaimsIdentity.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));
                    return(retVal);
                }
            }
            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;
            }
        }
Exemple #16
0
        /// <summary>
        /// Authenticate the user
        /// </summary>
        /// <param name="principal">Principal.</param>
        /// <param name="password">Password.</param>
        public System.Security.Principal.IPrincipal Authenticate(System.Security.Principal.IPrincipal principal, string password, String tfaSecret)
        {
            AuthenticatingEventArgs e = new AuthenticatingEventArgs(principal.Identity.Name);

            this.Authenticating?.Invoke(this, e);
            if (e.Cancel)
            {
                this.m_tracer.TraceWarning("Pre-Event ordered cancel of auth {0}", principal);
                return(null);
            }

            // Get the scope being requested
            String scope = "*";

            if (principal is ClaimsPrincipal)
            {
                scope = (principal as ClaimsPrincipal).Claims.FirstOrDefault(o => o.Type == OpenIzClaimTypes.OpenIzScopeClaim)?.Value ?? scope;
            }
            else
            {
                scope = ApplicationContext.Current.GetRestClient(Core.Interop.ServiceEndpointType.ImmunizationIntegrationService)?.Description.Endpoint[0].Address ??
                        ApplicationContext.Current.GetRestClient(Core.Interop.ServiceEndpointType.AdministrationIntegrationService)?.Description.Endpoint[0].Address ??
                        "*";
            }

            // Authenticate
            IPrincipal retVal = null;

            try
            {
                using (IRestClient restClient = ApplicationContext.Current.GetRestClient(Core.Interop.ServiceEndpointType.AuthenticationService))
                {
                    // Set credentials
                    restClient.Credentials = new OAuthTokenServiceCredentials(principal);

                    // Create grant information
                    OAuthTokenRequest request = null;
                    if (!String.IsNullOrEmpty(password))
                    {
                        request = new OAuthTokenRequest(principal.Identity.Name, password, scope);
                    }
                    else if (principal is TokenClaimsPrincipal)
                    {
                        request = new OAuthTokenRequest(principal as TokenClaimsPrincipal, scope);
                    }
                    else
                    {
                        request = new OAuthTokenRequest(principal.Identity.Name, null, scope);
                    }

                    try
                    {
                        restClient.Requesting += (o, p) =>
                        {
                            p.AdditionalHeaders.Add("X-OpenIZClient-Claim", Convert.ToBase64String(Encoding.UTF8.GetBytes(String.Format("{0}={1}", OpenIzClaimTypes.OpenIzScopeClaim, scope))));
                            if (!String.IsNullOrEmpty(tfaSecret))
                            {
                                p.AdditionalHeaders.Add("X-OpenIZ-TfaSecret", tfaSecret);
                            }
                        };

                        OAuthTokenResponse response = restClient.Post <OAuthTokenRequest, OAuthTokenResponse>("oauth2_token", "application/x-www-urlform-encoded", request);
                        retVal = new TokenClaimsPrincipal(response.AccessToken, response.TokenType, response.RefreshToken);
                        this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(principal.Identity.Name, retVal, true));
                    }
                    catch (RestClientException <OAuthTokenResponse> ex)
                    {
                        this.m_tracer.TraceWarning("OAUTH Server Responded: {0}", ex.Result.ErrorDescription);
                    }
                    catch (WebException ex) // Raw level web exception
                    {
                        this.m_tracer.TraceError("Error authenticating: {0}", ex.Message);
                    }
                    catch (SecurityException ex)
                    {
                        this.m_tracer.TraceError("Server was contacted however the token is invalid: {0}", ex.Message);
                        throw;
                    }
                    catch (Exception ex) // fallback to local
                    {
                        this.m_tracer.TraceError("General Authentication Error: {0}", ex.Message);
                    }
                }
            }
            catch
            {
                this.Authenticated?.Invoke(this, new AuthenticatedEventArgs(principal.Identity.Name, retVal, false));
                throw;
            }

            return(retVal);
        }
        /// <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");
            }
        }