/// <summary> /// Changes the users password. /// </summary> /// <param name="userName">The username of the user.</param> /// <param name="newPassword">The new password of the user.</param> /// <param name="principal">The authentication principal (the user that is changing the password).</param> public void ChangePassword(string userName, string newPassword, System.Security.Principal.IPrincipal principal) { try { // The principal must change their own password or must have the changepassword credential if (!userName.Equals(principal.Identity.Name, StringComparison.InvariantCultureIgnoreCase)) { new PolicyPermission(System.Security.Permissions.PermissionState.Unrestricted, PolicyIdentifiers.ChangePassword).Demand(); } else if (!principal.Identity.IsAuthenticated) { throw new InvalidOperationException("Unauthenticated principal cannot change user password"); } // Get the user's identity var securityUserService = ApplicationContext.Current.GetService <ISecurityRepositoryService>(); using (AmiServiceClient client = new AmiServiceClient(ApplicationContext.Current.GetRestClient("ami"))) { client.Client.Accept = "application/xml"; Guid userId = Guid.Empty; if (principal is ClaimsPrincipal) { var subjectClaim = (principal as ClaimsPrincipal).FindClaim(ClaimTypes.Sid); if (subjectClaim != null) { userId = Guid.Parse(subjectClaim.Value); } } // User ID not found - lookup if (userId == Guid.Empty) { // User service is null var securityUser = securityUserService.GetUser(principal.Identity); if (securityUser == null) { var tuser = client.GetUsers(o => o.UserName == principal.Identity.Name).CollectionItem.FirstOrDefault(); if (tuser == null) { throw new ArgumentException(string.Format("User {0} not found", userName)); } else { userId = tuser.UserId.Value; } } else { userId = securityUser.Key.Value; } } // Use the current configuration's credential provider var user = new SecurityUserInfo() { UserId = userId, UserName = userName, Password = newPassword }; // Set the credentials client.Client.Credentials = ApplicationContext.Current.Configuration.GetServiceDescription("ami").Binding.Security.CredentialProvider.GetCredentials(principal); client.UpdateUser(user.UserId.Value, user); var localIdp = new LocalIdentityService(); // Change locally localIdp.ChangePassword(userName, newPassword); // Audit - Local IDP has alerted this already if (!(localIdp is ISecurityAuditEventSource)) { this.SecurityAttributesChanged?.Invoke(this, new SecurityAuditDataEventArgs(user, "password")); } } } catch (Exception e) { this.m_tracer.TraceError("Error changing password for user {0} : {1}", userName, e); throw; } }
/// <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); }
/// <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 = new LocalRoleProviderService(); var localPip = new LocalPolicyInformationService(); var localIdp = new LocalIdentityService(); if (!String.IsNullOrEmpty(password) && principal is ClaimsPrincipal && XamarinApplicationContext.Current.ConfigurationManager.IsConfigured) { ClaimsPrincipal cprincipal = principal as ClaimsPrincipal; var amiPip = new AmiPolicyInformationService(cprincipal); // 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 == ClaimTypes.OpenIzGrantedPolicyClaim)) { if (localPip.GetPolicy(itm.Value) == null) { try { var policy = amiPip.GetPolicy(itm.Value); localPip.CreatePolicy(policy, new 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 == ClaimsIdentity.DefaultRoleClaimType)) { // Ensure policy exists try { var amiPolicies = amiPip.GetActivePolicies(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, new SystemPrincipal()); } } // Local role doesn't exist if (!localRoles.Contains(itm.Value)) { localRp.CreateRole(itm.Value, new SystemPrincipal()); } localRp.AddPoliciesToRoles(amiPolicies, new String[] { itm.Value }, new SystemPrincipal()); } catch (Exception e) { this.m_tracer.TraceWarning("Could not fetch / refresh policies: {0}", e.Message); } } var localUser = XamarinApplicationContext.Current.ConfigurationManager.IsConfigured ? localIdp.GetIdentity(principal.Identity.Name) : null; try { Guid sid = Guid.Parse(cprincipal.FindClaim(ClaimTypes.Sid).Value); if (localUser == null) { localIdp.CreateIdentity(sid, principal.Identity.Name, password, new SystemPrincipal()); } else { localIdp.ChangePassword(principal.Identity.Name, password, principal); } // Copy security attributes var localSu = ApplicationContext.Current.GetService <IDataPersistenceService <SecurityUser> >().Get(sid); localSu.Email = cprincipal.FindClaim(ClaimTypes.Email)?.Value; localSu.PhoneNumber = cprincipal.FindClaim(ClaimTypes.Telephone)?.Value; ApplicationContext.Current.GetService <IDataPersistenceService <SecurityUser> >().Update(localSu); // Add user to roles // TODO: Remove users from specified roles? localRp.AddUsersToRoles(new String[] { principal.Identity.Name }, cprincipal.Claims.Where(o => o.Type == ClaimsIdentity.DefaultRoleClaimType).Select(o => o.Value).ToArray(), new SystemPrincipal()); // Unlock the account localIdp.SetLockout(principal.Identity.Name, false); } catch (Exception ex) { this.m_tracer.TraceWarning("Insertion of local cache credential failed: {0}", ex); } } }