/// <summary> /// Processes all payroll for all organizations system-wide. Should run on the 1st of every month. /// </summary> public static void ProcessMonthly() { DateTime today = DateTime.UtcNow; string lastRun = Persistence.Key["LastSalaryRun"]; string expectedLastRun = today.ToString("yyyyMM", CultureInfo.InvariantCulture); if (lastRun != null && String.Compare(lastRun, expectedLastRun, CultureInfo.InvariantCulture, CompareOptions.IgnoreCase) >= 0) { // nothing to do, return return; } Persistence.Key["LastSalaryRun"] = expectedLastRun; // Process the payroll for all organizations. Assume payday is 25th. // TODO: Different payday per organization? Payroll payroll = GetAll(); DateTime payday = new DateTime(today.Year, today.Month, 25); foreach (PayrollItem payrollItem in payroll) { Salary salary = Salary.Create(payrollItem, payday); SwarmopsLog.CreateEntry(payrollItem.Person, new SalaryCreatedLogEntry(salary)); // TODO: CREATE SALARY SPECIFICATION, SEND TO PERSON } }
protected void ButtonSubmit_Click(object sender, EventArgs e) { DateTime dateOfBirth = new DateTime(1800, 1, 1); // null equivalent if (this.TextDateOfBirth.Text.Length > 0) { dateOfBirth = DateTime.Parse(this.TextDateOfBirth.Text); } string street = this.TextStreet1.Text; if (!string.IsNullOrEmpty(this.TextStreet2.Text)) { street += "|" + this.TextStreet2.Text; } Person newPerson = Person.Create(this.TextName.Text, this.TextMail.Text, string.Empty, this.TextPhone.Text, street, this.TextPostal.Text, this.TextCity.Text, this.DropCountries.SelectedValue, dateOfBirth, (PersonGender)Enum.Parse(typeof(PersonGender), this.DropGenders.SelectedValue)); DateTime participationExpiry = Constants.DateTimeHigh; ParticipantMailType welcomeMailType = ParticipantMailType.ParticipantAddedWelcome_NoExpiry; int participationDurationMonths = Int32.Parse(CurrentOrganization.Parameters.ParticipationDuration); if (participationDurationMonths < 1000) { participationExpiry = DateTime.Today.AddMonths(participationDurationMonths); welcomeMailType = ParticipantMailType.ParticipantAddedWelcome; } Participation newParticipation = Participation.Create(newPerson, CurrentOrganization, participationExpiry); OutboundComm.CreateParticipantMail(welcomeMailType, newParticipation, CurrentUser); newPerson.LastLogonOrganizationId = CurrentOrganization.Identity; SwarmopsLogEntry logEntry = SwarmopsLog.CreateEntry(newPerson, new Swarmops.Logic.Support.LogEntries.PersonAddedLogEntry(newParticipation, CurrentUser)); logEntry.CreateAffectedObject(newParticipation); logEntry.CreateAffectedObject(CurrentUser); // Clear form and make way for next person this.TextName.Text = string.Empty; this.TextStreet1.Text = string.Empty; this.TextStreet2.Text = string.Empty; this.TextMail.Text = string.Empty; this.TextPhone.Text = string.Empty; this.TextPostal.Text = string.Empty; this.TextCity.Text = string.Empty; this.TextDateOfBirth.Text = string.Empty; this.DropGenders.SelectedValue = "Unknown"; this.TextName.Focus(); this.LiteralLoadAlert.Text = Resources.Pages.Swarm.AddPerson_PersonSuccessfullyRegistered; }
public static AjaxCallResult Commence(int personId) { AuthenticationData authData = GetAuthenticationDataAndCulture(); if (!authData.Authority.HasSystemAccess()) { // Restrict impersonation to system-level access for now: it's a debugging tool return(new AjaxCallResult { Success = false, DisplayMessage = CommonV5.JavascriptEscape(Resources.Pages.Admin.CommenceImpersonation_Failed) }); } // BEGIN IMPERSONATION Person impersonatedPerson = Person.FromIdentity(personId); SwarmopsLogEntry newEntry = SwarmopsLog.CreateEntry(impersonatedPerson, new ImpersonationLogEntry { ImpersonatorPersonId = authData.CurrentUser.PersonId, Started = true }); newEntry.CreateAffectedObject(authData.CurrentUser); // link impersonator to log entry for searchability // Someone who has system level access can always impersonate => no further access control at this time // SECURITY CONSIDERATIONS: If somebody replaces/fires a superior? Trivially undoable at the database level DateTime utcNow = DateTime.UtcNow; Authority impersonatingAuthority = Authority.FromLogin(impersonatedPerson, authData.CurrentOrganization); impersonatingAuthority.Impersonation = new Impersonation { ImpersonatedByPersonId = authData.CurrentUser.PersonId, ImpersonationStarted = utcNow }; FormsAuthentication.SetAuthCookie(impersonatingAuthority.ToEncryptedXml(), false); HttpContext.Current.Response.AppendCookie(new HttpCookie("DashboardMessage", CommonV5.JavascriptEscape(String.Format(Resources.Pages.Admin.CommenceImpersonation_Success, utcNow)))); return(new AjaxCallResult { Success = true }); }
protected void ButtonSubmit_Click(object sender, EventArgs e) { DateTime dateOfBirth = new DateTime(1800, 1, 1); // null equivalent if (this.TextDateOfBirth.Text.Length > 0) { dateOfBirth = DateTime.Parse(this.TextDateOfBirth.Text); } string street = this.TextStreet1.Text; if (!string.IsNullOrEmpty(this.TextStreet2.Text)) { street += "|" + this.TextStreet2.Text; } Person newPerson = Person.Create(this.TextName.Text, this.TextMail.Text, string.Empty, this.TextPhone.Text, street, this.TextPostal.Text, this.TextCity.Text, this.DropCountries.SelectedValue, dateOfBirth, (PersonGender)Enum.Parse(typeof(PersonGender), this.DropGenders.SelectedValue)); Membership newMembership = Membership.Create(newPerson, CurrentOrganization, DateTime.Today.AddYears(1)); OutboundComm.CreateMembershipLetter(ParticipantMailType.MemberAddedWelcome, newMembership, CurrentUser); SwarmopsLogEntry logEntry = SwarmopsLog.CreateEntry(newPerson, new Swarmops.Logic.Support.LogEntries.PersonAddedLogEntry(newMembership, CurrentUser)); logEntry.CreateAffectedObject(newMembership); logEntry.CreateAffectedObject(CurrentUser); // Clear form and make way for next person this.TextName.Text = string.Empty; this.TextStreet1.Text = string.Empty; this.TextStreet2.Text = string.Empty; this.TextMail.Text = string.Empty; this.TextPhone.Text = string.Empty; this.TextPostal.Text = string.Empty; this.TextCity.Text = string.Empty; this.TextDateOfBirth.Text = string.Empty; this.DropGenders.SelectedValue = "Unknown"; this.TextName.Focus(); this.LiteralLoadAlert.Text = Resources.Pages.Swarm.AddPerson_PersonSuccessfullyRegistered; }
public static bool RequestTicket(string mailAddress) { mailAddress = mailAddress.Trim(); if (string.IsNullOrEmpty(mailAddress)) { return(false); // this is the only case when we return false: a _syntactically_invalid_ address } People concernedPeople = People.FromMail(mailAddress); // Should result in exactly 1 if (concernedPeople.Count != 1) { return(true); // TODO: Prevent registration with duplicate mail addy, or this will cause problems down the road } Person concernedPerson = concernedPeople[0]; if (!string.IsNullOrEmpty(concernedPerson.BitIdAddress)) { // Cannot reset password - two factor auth is enabled. Manual intervention required. OutboundComm.CreateSecurityNotification(concernedPerson, null, null, string.Empty, NotificationResource.Password_CannotReset2FA); return(true); // still returning true - the fail info is in mail only } string resetTicket = SupportFunctions.GenerateSecureRandomKey(16); resetTicket = resetTicket.Substring(0, 21); // We're using a 21-character (84-bit) key mostly for UI consistency with the ticket sent in mail, and it's secure enough concernedPerson.ResetPasswordTicket = DateTime.UtcNow.AddHours(1).ToString(CultureInfo.InvariantCulture) + "," + resetTicket; // Adds expiry - one hour OutboundComm.CreateSecurityNotification(concernedPerson, null, null, resetTicket, NotificationResource.Password_ResetOnRequest); SwarmopsLog.CreateEntry(null, new PasswordResetRequestLogEntry(concernedPerson, SupportFunctions.GetRemoteIPAddressChain())); return(true); }
public static AjaxCallResult TerminateImpersonation() { AuthenticationData authData = GetAuthenticationDataAndCulture(); if (!authData.Authority.ImpersonationActive) { return(new AjaxCallResult { Success = false }); // no impersonation active. Race condition? } int realUserPersonId = authData.Authority.Impersonation.ImpersonatedByPersonId; Person impersonator = Person.FromIdentity(realUserPersonId); // Terminate impersonation and set new authority cookie from the impersonator data. // VERY SECURITY SENSITIVE: The identity as impersonator will be the new user. // TODO: LOG LOG LOG LOG SwarmopsLogEntry logEntry = SwarmopsLog.CreateEntry(authData.CurrentUser, new ImpersonationLogEntry { ImpersonatorPersonId = impersonator.Identity, Started = false }); logEntry.CreateAffectedObject(impersonator); // link impersonator to log entry for searchability DateTime utcNow = DateTime.UtcNow; Authority authority = Authority.FromLogin(impersonator, authData.CurrentOrganization); FormsAuthentication.SetAuthCookie(authority.ToEncryptedXml(), false); HttpContext.Current.Response.AppendCookie(new HttpCookie("DashboardMessage", CommonV5.JavascriptEscape(String.Format(Resources.Pages.Admin.CommenceImpersonation_Ended, utcNow)))); // returning Success will force a reload, resetting dashboard to original user return(new AjaxCallResult { Success = true }); }
public static AjaxCallResult SignupParticipant(string name, int organizationId, string mail, string password, string phone, string street1, string street2, string postalCode, string city, string countryCode, string dateOfBirth, int geographyId, bool activist, PersonGender gender, int[] positionIdsVolunteer) { CommonV5.CulturePreInit(HttpContext.Current.Request); // Set culture, for date parsing if (geographyId == 0) { geographyId = Geography.RootIdentity; // if geo was undetermined, set it to "Global" } Organization organization = Organization.FromIdentity(organizationId); DateTime parsedDateOfBirth = new DateTime(1800, 1, 1); // Default if unspecified if (dateOfBirth.Length > 0) { parsedDateOfBirth = DateTime.Parse(dateOfBirth); } Person newPerson = Person.Create(name, mail, password, phone, street1 + "\n" + street2.Trim(), postalCode, city, countryCode, parsedDateOfBirth, gender); Participation participation = newPerson.AddParticipation(organization, DateTime.UtcNow.AddYears(1)); // TODO: set duration from organization settings of Participantship // TODO: SEND NOTIFICATIONS // Log the signup SwarmopsLog.CreateEntry(newPerson, new PersonAddedLogEntry(participation, newPerson)); // Create notification OutboundComm.CreateParticipantNotification(newPerson, newPerson, organization, NotificationResource.Participant_Signup); // Add the bells and whistles if (activist) { newPerson.CreateActivist(false, false); } if (positionIdsVolunteer.Length > 0) { Volunteer volunteer = newPerson.CreateVolunteer(); foreach (int positionId in positionIdsVolunteer) { Position position = Position.FromIdentity(positionId); volunteer.AddPosition(position); SwarmopsLog.CreateEntry(newPerson, new VolunteerForPositionLogEntry(newPerson, position)); } } newPerson.LastLogonOrganizationId = organizationId; // Create a welcome message to the Dashboard HttpContext.Current.Response.AppendCookie(new HttpCookie("DashboardMessage", CommonV5.JavascriptEscape(String.Format(Resources.Pages.Public.Signup_DashboardMessage, organization.Name)))); // Set authentication cookie, which will log the new person in using the credentials just given FormsAuthentication.SetAuthCookie(Authority.FromLogin(newPerson).ToEncryptedXml(), true); AjaxCallResult result = new AjaxCallResult { Success = true }; return(result); }
public static bool PerformReset(string mailAddress, string ticket, string newPassword) { People people = People.FromMail(mailAddress.Trim()); if (people.Count != 1) { return(false); } Person resetPerson = people[0]; string[] resetData = resetPerson.ResetPasswordTicket.Split(','); if (resetData.Length != 2) { return(false); // invalid data or no ticket } // This may throw on invalid data, which will give a slightly different error but that's fine too for now. DateTime ticketExpiresUtc = DateTime.Parse(resetData[0]); if (DateTime.UtcNow > ticketExpiresUtc) { // Ticket expired. return(false); } if (ticket != resetData[1]) { // Bad ticket. return(false); } // When we get here, all checks to reset the password have completed. Change the password, log the change, // notify the user that the password was changed, set a new authentication cookie, and have the web page // redirect to Dashboard (by returning true). // Clear password reset ticket resetPerson.ResetPasswordTicket = string.Empty; // Create lockdown code, notify string lockdownTicket = SupportFunctions.GenerateSecureRandomKey(16); OutboundComm.CreateSecurityNotification(resetPerson, null, null, lockdownTicket, NotificationResource.Password_Changed); resetPerson.AccountLockdownTicket = DateTime.UtcNow.AddDays(1).ToString(CultureInfo.InvariantCulture) + "," + lockdownTicket; // Set new password resetPerson.SetPassword(newPassword); // Log the password reset SwarmopsLog.CreateEntry(resetPerson, new PasswordResetLogEntry(resetPerson, SupportFunctions.GetRemoteIPAddressChain())); // Set authentication cookies int lastOrgId = resetPerson.LastLogonOrganizationId; if (lastOrgId == 0) { lastOrgId = Organization.SandboxIdentity; } if (!resetPerson.ParticipatesInOrganizationOrParent(lastOrgId)) { // If the person doesn't have access to the last organization (anymore), log on to Sandbox lastOrgId = 1; } // Set cookies FormsAuthentication.SetAuthCookie(Authority.FromLogin(resetPerson).ToEncryptedXml(), true); DashboardMessage.Set(Resources.Pages.Security.ResetPassword_Success); return(true); // temp // do NOT NOT NOT trim password - this is deliberate. Passwords starting/ending in whitespace must be possible }
public static AjaxInputCallResult SetPersonEditorData(int personId, string field, string newValue) { if (newValue == null || field == null) { throw new ArgumentNullException(); } AuthenticationData authData = GetAuthenticationDataAndCulture(); bool self = false; // Are we modifying ourselves? if (personId == 0) // request self record { self = true; // may make use of this later personId = authData.CurrentUser.Identity; } // Preliminary input validation if (string.IsNullOrEmpty(newValue)) { if (field != "TwitterId") // These fields may be set to empty; default is disallow { return(new AjaxInputCallResult { Success = false, ObjectIdentity = personId, DisplayMessage = Resources.Global.Global_FieldCannotBeEmpty, FailReason = AjaxInputCallResult.ErrorInvalidFormat, NewValue = GetPersonValue(personId, field) }); } } // Verify authority to see and change personal data Person affectedPerson = Person.FromIdentity(personId); if (!self) { if (!authData.Authority.CanSeePerson(affectedPerson) || !authData.Authority.HasAccess(new Access(authData.CurrentOrganization, affectedPerson.Geography, AccessAspect.PersonalData))) { throw new UnauthorizedAccessException(); } } string oldValue; string displayMessage = string.Empty; while (newValue.Contains(" ")) { newValue = newValue.Trim().Replace(" ", " "); // double, triple, quadruple spaces reduced to one } switch (field) { case "Name": oldValue = affectedPerson.Name; affectedPerson.Name = newValue; break; case "Mail": oldValue = affectedPerson.Mail; affectedPerson.Mail = newValue; break; case "Phone": oldValue = affectedPerson.Phone; affectedPerson.Phone = newValue; if (!Regex.IsMatch(newValue, @"^[0-9 \(\)\-\+]+$")) { // using characters not typically seen in a phone number? Warn displayMessage = Resources.Global.Master_EditPersonWarning_Phone; } break; case "TwitterId": if (newValue.StartsWith("@")) { newValue = newValue.Substring(1); } oldValue = affectedPerson.TwitterId; affectedPerson.TwitterId = newValue; break; default: throw new ArgumentException("Unrecognized field in /Automation/SwarmFunctions.SetPersonEditorData"); } SwarmopsLogEntry logEntry = SwarmopsLog.CreateEntry(affectedPerson, new PersonalDataChangedLogEntry { ActingPersonId = authData.CurrentUser.PersonId, AffectedPersonId = affectedPerson.PersonId, Field = field, IpAddress = SupportFunctions.GetMostLikelyRemoteIPAddress(), OldValue = oldValue, NewValue = newValue }); if (!self) { logEntry.CreateAffectedObject(authData.CurrentUser); } return(new AjaxInputCallResult { ObjectIdentity = personId, Success = true, NewValue = newValue, DisplayMessage = displayMessage }); }