/// <summary> /// Synchronize security locally /// </summary> private void SynchronizeSecurity(string deviceSecret, string deviceName, IPrincipal principal) { // Create a security user and ensure they exist! var localPip = ApplicationContext.Current.GetService <IOfflinePolicyInformationService>(); var localIdp = ApplicationContext.Current.GetService <IOfflineDeviceIdentityProviderService>(); var sdPersistence = ApplicationContext.Current.GetService <IDataPersistenceService <SecurityDevice> >(); if (localIdp != null && !String.IsNullOrEmpty(deviceSecret) && principal is IClaimsPrincipal && ApplicationContext.Current.ConfigurationPersister.IsConfigured) { using (AuthenticationContext.EnterContext(principal)) { IClaimsPrincipal cprincipal = principal as IClaimsPrincipal; var amiPip = new AmiPolicyInformationService(); IIdentity localDeviceIdentity = null; lock (this.m_lockObject) { try { localDeviceIdentity = localIdp.GetIdentity(deviceName); Guid sid = Guid.Parse(cprincipal.FindFirst(SanteDBClaimTypes.SanteDBDeviceIdentifierClaim).Value); if (localDeviceIdentity == null) { localDeviceIdentity = localIdp.CreateIdentity(sid, deviceName, deviceSecret, AuthenticationContext.SystemPrincipal); } else { localIdp.ChangeSecret(deviceName, deviceSecret, AuthenticationContext.SystemPrincipal); } } catch (Exception ex) { this.m_tracer.TraceWarning("Insertion of local cache credential failed: {0}", ex); } // Ensure policies exist from the claim foreach (var itm in cprincipal.Claims.Where(o => o.Type == SanteDBClaimTypes.SanteDBGrantedPolicyClaim)) { if (localPip.GetPolicy(itm.Value) == null) { try { var policy = amiPip.GetPolicy(itm.Value); localPip.CreatePolicy(policy, AuthenticationContext.SystemPrincipal); } catch (Exception e) { this.m_tracer.TraceWarning("Cannot update local policy information : {0}", e.Message); } } } localPip.AddPolicies(localDeviceIdentity, PolicyGrantType.Grant, AuthenticationContext.SystemPrincipal, cprincipal.Claims.Where(o => o.Type == SanteDBClaimTypes.SanteDBGrantedPolicyClaim).Select(o => o.Value).ToArray()); } } } }
/// <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> /// Synchronize the security settings /// </summary> /// <param name="password"></param> /// <param name="principal"></param> private void SynchronizeSecurity(string password, IPrincipal principal) { // Create a security user and ensure they exist! var localRp = ApplicationContext.Current.GetService <IOfflineRoleProviderService>(); var localPip = ApplicationContext.Current.GetService <IOfflinePolicyInformationService>(); var localIdp = ApplicationContext.Current.GetService <IOfflineIdentityProviderService>(); if (localRp == null || localPip == null || localIdp == null) { return; } if (!String.IsNullOrEmpty(password) && principal is IClaimsPrincipal && ApplicationContext.Current.ConfigurationPersister.IsConfigured) { using (AuthenticationContext.EnterContext(principal)) { IClaimsPrincipal cprincipal = principal as IClaimsPrincipal; var amiPip = new AmiPolicyInformationService(); // We want to impersonate SYSTEM //AndroidApplicationContext.Current.SetPrincipal(cprincipal); // Ensure policies exist from the claim foreach (var itm in cprincipal.Claims.Where(o => o.Type == SanteDBClaimTypes.SanteDBGrantedPolicyClaim).ToArray()) { if (localPip.GetPolicy(itm.Value) == null) { try { var policy = amiPip.GetPolicy(itm.Value); localPip.CreatePolicy(policy, AuthenticationContext.SystemPrincipal); } catch (Exception e) { this.m_tracer.TraceWarning("Cannot update local policy information : {0}", e.Message); } } } // Ensure roles exist from the claim var localRoles = localRp.GetAllRoles(); foreach (var itm in cprincipal.Claims.Where(o => o.Type == SanteDBClaimTypes.DefaultRoleClaimType).ToArray()) { // Ensure policy exists try { var amiPolicies = amiPip.GetPolicies(new SecurityRole() { Name = itm.Value }).ToArray(); foreach (var pol in amiPolicies) { if (localPip.GetPolicy(pol.Policy.Oid) == null) { var policy = amiPip.GetPolicy(pol.Policy.Oid); localPip.CreatePolicy(policy, AuthenticationContext.SystemPrincipal); } } // Local role doesn't exist if (!localRoles.Contains(itm.Value)) { localRp.CreateRole(itm.Value, AuthenticationContext.SystemPrincipal); } localRp.AddPoliciesToRoles(amiPolicies, new String[] { itm.Value }, AuthenticationContext.SystemPrincipal); } catch (Exception e) { this.m_tracer.TraceWarning("Could not fetch / refresh policies: {0}", e.Message); } } var localUser = ApplicationContext.Current.ConfigurationPersister.IsConfigured ? localIdp.GetIdentity(principal.Identity.Name) : null; try { Guid sid = Guid.Parse(cprincipal.FindFirst(SanteDBClaimTypes.Sid).Value); if (localUser == null) { localIdp.CreateIdentity(sid, principal.Identity.Name, password, AuthenticationContext.SystemPrincipal); } else { localIdp.ChangePassword(principal.Identity.Name, password, AuthenticationContext.SystemPrincipal); } // Copy security attributes var localSu = ApplicationContext.Current.GetService <IDataPersistenceService <SecurityUser> >().Get(sid, null, true, AuthenticationContext.SystemPrincipal); localSu.Email = cprincipal.FindFirst(SanteDBClaimTypes.Email)?.Value; localSu.PhoneNumber = cprincipal.FindFirst(SanteDBClaimTypes.Telephone)?.Value; localSu.LastLoginTime = DateTime.Now; ApplicationContext.Current.GetService <IDataPersistenceService <SecurityUser> >().Update(localSu, TransactionMode.Commit, AuthenticationContext.SystemPrincipal); // Add user to roles // TODO: Remove users from specified roles? localRp.AddUsersToRoles(new String[] { principal.Identity.Name }, cprincipal.Claims.Where(o => o.Type == SanteDBClaimTypes.DefaultRoleClaimType).Select(o => o.Value).ToArray(), AuthenticationContext.SystemPrincipal); // Unlock the account localIdp.SetLockout(principal.Identity.Name, false, principal); } catch (Exception ex) { this.m_tracer.TraceWarning("Insertion of local cache credential failed: {0}", ex); } } } }