namespace IssueVision.Data.Web
{
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Data;
using System.Linq;
using System.ServiceModel.DomainServices.EntityFramework;
using System.ServiceModel.DomainServices.Hosting;
using System.ServiceModel.DomainServices.Server;
// Implements application logic using the IssueVisionEntities context.
// TODO: Add your application logic to these methods or in additional methods.
// TODO: Wire up authentication (Windows/ASP.NET Forms) and uncomment the following to disable anonymous access
// Also consider adding roles to restrict access as appropriate.
[EnableClientAccess()]
[RequiresAuthentication]
public class IssueVisionService : LinqToEntitiesDomainService<IssueVisionEntities>
{
public IQueryable<Attribute> GetAttributes()
{
return this.ObjectContext.Attributes;
}
public void InsertAttribute(Attribute attribute)
{
attribute.ID = Guid.NewGuid();
if ((attribute.EntityState != EntityState.Detached))
{
this.ObjectContext.ObjectStateManager.ChangeObjectState(attribute, EntityState.Added);
}
else
{
this.ObjectContext.Attributes.AddObject(attribute);
}
}
public void UpdateAttribute(Attribute currentAttribute)
{
this.ObjectContext.Attributes.AttachAsModified(currentAttribute, this.ChangeSet.GetOriginal(currentAttribute));
}
public void DeleteAttribute(Attribute attribute)
{
if ((attribute.EntityState == EntityState.Detached))
{
this.ObjectContext.Attributes.Attach(attribute);
}
this.ObjectContext.Attributes.DeleteObject(attribute);
}
public IQueryable<File> GetFiles()
{
return this.ObjectContext.Files;
}
public void InsertFile(File file)
{
if ((file.EntityState != EntityState.Detached))
{
this.ObjectContext.ObjectStateManager.ChangeObjectState(file, EntityState.Added);
}
else
{
this.ObjectContext.Files.AddObject(file);
}
}
public void UpdateFile(File currentFile)
{
this.ObjectContext.Files.AttachAsModified(currentFile, this.ChangeSet.GetOriginal(currentFile));
}
public void DeleteFile(File file)
{
if ((file.EntityState == EntityState.Detached))
{
this.ObjectContext.Files.Attach(file);
}
this.ObjectContext.Files.DeleteObject(file);
}
/// <summary>
/// Get all issues orderby by StatusID, and Priority
/// </summary>
/// <returns></returns>
public IQueryable<Issue> GetIssues()
{
return this.ObjectContext.Issues
.Include("Files").Include("Attributes")
.OrderBy(g => g.StatusID).ThenBy(g => g.Priority);
}
/// <summary>
/// Get all un-resolved issues
/// </summary>
/// <returns></returns>
public IQueryable<Issue> GetAllUnResolvedIssues()
{
return this.ObjectContext.Issues
.Where(n => (n.ResolutionID == null || n.ResolutionID == 0));
}
/// <summary>
/// Get all issues for the current user orderby by StatusID, and Priority
/// </summary>
/// <returns></returns>
public IQueryable<Issue> GetMyIssues()
{
return this.ObjectContext.Issues
.Include("Files").Include("Attributes")
.Where(g => g.AssignedToID.Equals(this.ServiceContext.User.Identity.Name))
.OrderBy(g => g.StatusID).ThenBy(g => g.Priority);
}
/// <summary>
/// When a new issue is created, we should check the following:
///
/// 1) OpenedDate, OpenedByID, LastChange, changedByID, and IssueID
/// should be set with their initial values.
/// 2) If status is Open, the AssignToID should be null.
/// 3) ResolutionDate and ResolvedByID should be set based on ResolutionID.
/// </summary>
/// <param name="issue"></param>
public void InsertIssue(Issue issue)
{
issue.OpenedDate = DateTime.Now;
issue.OpenedByID = this.ServiceContext.User.Identity.Name;
issue.LastChange = DateTime.Now;
issue.ChangedByID = this.ServiceContext.User.Identity.Name;
// create a new Issue ID
issue.IssueID = ObjectContext.Issues.Count() > 0 ? (from iss in ObjectContext.Issues select iss.IssueID).Max() + 1 : 1;
// if status is Open, AssignedToID should be null
if (issue.StatusID == IssueVisionServiceConstant.OpenStatusID)
{
issue.AssignedToID = null;
}
// set ResolutionDate and ResolvedByID based on ResolutionID
if (issue.ResolutionID == null || issue.ResolutionID == 0)
{
issue.ResolutionDate = null;
issue.ResolvedByID = null;
}
else
{
issue.ResolutionDate = DateTime.Now;
issue.ResolvedByID = this.ServiceContext.User.Identity.Name;
}
if ((issue.EntityState != EntityState.Detached))
{
this.ObjectContext.ObjectStateManager.ChangeObjectState(issue, EntityState.Added);
}
else
{
this.ObjectContext.Issues.AddObject(issue);
}
}
/// <summary>
/// When a new issue is updated, we should check the following:
///
/// 1) LastChange and changedByID should be upated.
/// 2) If status is Open, the AssignToID should be null.
/// 3) ResolutionDate and ResolvedByID should be set based on ResolutionID.
/// </summary>
/// <param name="issue"></param>
public void UpdateIssue(Issue issue)
{
// Business logic:
// Admin user can read/update any issue, and
// normal user can only read/update issues assigned to them
// or issues created by them and have not assigned to anyone.
if (!IssueIsReadOnly(this.ChangeSet.GetOriginal(issue)))
{
issue.LastChange = DateTime.Now;
issue.ChangedByID = this.ServiceContext.User.Identity.Name;
// if status is Open, AssignedToID should be null
if (issue.StatusID == IssueVisionServiceConstant.OpenStatusID)
{
issue.AssignedToID = null;
}
// set ResolutionDate and ResolvedByID based on ResolutionID
if (issue.ResolutionID == null || issue.ResolutionID == 0)
{
issue.ResolutionDate = null;
issue.ResolvedByID = null;
}
else
{
issue.ResolutionDate = DateTime.Now;
issue.ResolvedByID = this.ServiceContext.User.Identity.Name;
}
this.ObjectContext.Issues.AttachAsModified(issue, this.ChangeSet.GetOriginal(issue));
}
else
throw new ValidationException(ErrorResources.NoPermissionToUpdateIssue);
}
public void DeleteIssue(Issue issue)
{
if ((issue.EntityState == EntityState.Detached))
{
this.ObjectContext.Issues.Attach(issue);
}
this.ObjectContext.Issues.DeleteObject(issue);
}
private class ServerBugCount
{
public Int32 Month { get; set; }
public Int32 Year { get; set; }
public Int32 Count { get; set; }
}
/// <summary>
/// Return the active bug count for the last numberOfMonth months
/// </summary>
/// <param name="numberOfMonth"></param>
/// <returns></returns>
[Invoke]
public string[] GetActiveBugCountByMonth(Int32 numberOfMonth)
{
var issueCountsByMonth = from n in this.ObjectContext.Issues
where (n.ResolutionID == null || n.ResolutionID == 0)
group n by new { month = n.OpenedDate.Month, year = n.OpenedDate.Year } into d
select new ServerBugCount {
Month = d.Key.month,
Year = d.Key.year,
Count = d.Count()
};
List<string> resultList = new List<string>();
// loop through issueCountsByMonth for the last numberOfMonth
for (int i = numberOfMonth; i >= 1 ; i--)
{
DateTime dt = DateTime.Today.AddMonths(-i);
// search issueCountsByMonth for the issue counts of a specific month
ServerBugCount currentIssueCount = issueCountsByMonth
.Where(n => (n.Month == dt.Month && n.Year == dt.Year))
.FirstOrDefault();
if (currentIssueCount == null)
{
// the active issue count for that month is zero
resultList.Add(dt.ToString("MM/yyyy") + "/0");
}
else
{
// the active issue count for that month is not zero
resultList.Add(currentIssueCount.Month.ToString("00") + "/" + currentIssueCount.Year.ToString() + "/" + currentIssueCount.Count.ToString());
}
}
return resultList.ToArray();
}
/// <summary>
/// Return the resolved bug count for the last numberOfMonth months
/// </summary>
/// <param name="numberOfMonth"></param>
/// <returns></returns>
[Invoke]
public string[] GetResolvedBugCountByMonth(Int32 numberOfMonth)
{
var issueCountsByMonth = from n in this.ObjectContext.Issues
where (n.ResolutionID != null && n.ResolutionID != 0)
group n by new
{
month = (n.ResolutionDate ?? DateTime.Now).Month,
year = (n.ResolutionDate ?? DateTime.Now).Year
} into d
select new ServerBugCount
{
Month = d.Key.month,
Year = d.Key.year,
Count = d.Count()
};
List<string> resultList = new List<string>();
// loop through issueCountsByMonth for the last numberOfMonth
for (int i = numberOfMonth; i >= 1; i--)
{
DateTime dt = DateTime.Today.AddMonths(-i);
// search issueCountsByMonth for the issue counts of a specific month
ServerBugCount currentIssueCount = issueCountsByMonth
.Where(n => (n.Month == dt.Month && n.Year == dt.Year))
.FirstOrDefault();
if (currentIssueCount == null)
{
// the resolved issue count for that month is zero
resultList.Add(dt.ToString("MM/yyyy") + "/0");
}
else
{
// the resolved issue count for that month is not zero
resultList.Add(currentIssueCount.Month.ToString("00") + "/" + currentIssueCount.Year.ToString() + "/" + currentIssueCount.Count.ToString());
}
}
return resultList.ToArray();
}
private class ServerBugCountByPriority
{
public Int32 Priority { get; set; }
public Int32 Count { get; set; }
}
/// <summary>
/// Return the active bug count by priority
/// </summary>
/// <param name="numberOfMonth"></param>
/// <returns></returns>
[Invoke]
public string[] GetActiveBugCountByPriority()
{
var issueCountsByPriority = from n in this.ObjectContext.Issues
where (n.ResolutionID == null || n.ResolutionID == 0)
group n by new { priority = n.Priority } into d
select new ServerBugCountByPriority
{
Priority = d.Key.priority,
Count = d.Count()
};
List<string> resultList = new List<string>();
// loop through issueCountsByPriority
for (byte i = IssueVisionServiceConstant.HighestPriority; i <= IssueVisionServiceConstant.LowestPriority; i++)
{
// search issueCountsByPriority for the issue counts of a specific priority
ServerBugCountByPriority currentIssueCount = issueCountsByPriority
.Where(n => (n.Priority == i))
.FirstOrDefault();
if (currentIssueCount == null)
{
// the active issue count for that priority is zero
resultList.Add(i.ToString() + "/0");
}
else
{
// the active issue count for that priority is not zero
resultList.Add(currentIssueCount.Priority.ToString() + "/" + currentIssueCount.Count.ToString());
}
}
return resultList.ToArray();
}
public IQueryable<IssueHistory> GetIssueHistories()
{
return this.ObjectContext.IssueHistories;
}
public IQueryable<IssueType> GetIssueTypes()
{
return this.ObjectContext.IssueTypes;
}
public IQueryable<Platform> GetPlatforms()
{
return this.ObjectContext.Platforms;
}
public IQueryable<Resolution> GetResolutions()
{
return this.ObjectContext.Resolutions;
}
public IQueryable<SecurityQuestion> GetSecurityQuestions()
{
return this.ObjectContext.SecurityQuestions;
}
public IQueryable<Status> GetStatuses()
{
return this.ObjectContext.Statuses;
}
public IQueryable<SubStatus> GetSubStatuses()
{
return this.ObjectContext.SubStatuses;
}
public IQueryable<User> GetUsers()
{
return this.ObjectContext.Users;
}
/// <summary>
/// Get the user information for the currently login user
/// </summary>
/// <returns></returns>
[Query(IsComposable = false)]
public User GetCurrentUser()
{
return this.ObjectContext.Users.FirstOrDefault(u => u.Name == this.ServiceContext.User.Identity.Name);
}
public void InsertUser(User user)
{
// check for insert user permission
if (CheckUserInsertPermission(user) && user.IsUserMaintenance)
{
// Re-generate password hash and password salt
user.PasswordSalt = HashHelper.CreateRandomSalt();
user.PasswordHash = HashHelper.ComputeSaltedHash(user.NewPassword, user.PasswordSalt);
// set a valid PasswordQuestion
SecurityQuestion securityQuestion = this.ObjectContext.SecurityQuestions.FirstOrDefault();
if (securityQuestion != null)
user.PasswordQuestion = securityQuestion.PasswordQuestion;
// set PasswordAnswer that no body knows
user.PasswordAnswerSalt = HashHelper.CreateRandomSalt();
user.PasswordAnswerHash = HashHelper.CreateRandomSalt();
// requires the user to reset profile
user.ProfileReset = (byte)1;
if ((user.EntityState != EntityState.Detached))
{
this.ObjectContext.ObjectStateManager.ChangeObjectState(user, EntityState.Added);
}
else
{
this.ObjectContext.Users.AddObject(user);
}
}
else
throw new ValidationException(ErrorResources.NoPermissionToInsertUser);
}
public void UpdateUser(User currentUser)
{
if (currentUser.IsUserMaintenance)
{
// the call is from UserMaintenance screen
if (currentUser.Name == this.ServiceContext.User.Identity.Name)
{ // the caller is updateing itself
// Search user from database by name
User foundUser = this.ObjectContext.Users.FirstOrDefault(u => u.Name == currentUser.Name);
if (foundUser != null)
{
// verify whether the caller is admin
if (IsAdminUser(foundUser))
{
// Re-generate password hash and password salt
foundUser.PasswordSalt = HashHelper.CreateRandomSalt();
foundUser.PasswordHash = HashHelper.ComputeSaltedHash(currentUser.NewPassword, foundUser.PasswordSalt);
foundUser.FirstName = currentUser.FirstName;
foundUser.LastName = currentUser.LastName;
foundUser.Email = currentUser.Email;
foundUser.UserType = currentUser.UserType;
// requires the foundUser to reset profile
foundUser.ProfileReset = (byte)1;
}
else
throw new ValidationException(ErrorResources.NoPermissionToUpdateUser);
}
else
throw new ValidationException(ErrorResources.NoPermissionToUpdateUser);
}
else
{ // the caller is updating someone else
// verify whether the caller is admin
if (IsAdminUser(GetUserByName(this.ServiceContext.User.Identity.Name)))
{
// Re-generate password hash and password salt
currentUser.PasswordSalt = HashHelper.CreateRandomSalt();
currentUser.PasswordHash = HashHelper.ComputeSaltedHash(currentUser.NewPassword, currentUser.PasswordSalt);
// requires the currentUser to reset profile
currentUser.ProfileReset = (byte)1;
this.ObjectContext.Users.AttachAsModified(currentUser, this.ChangeSet.GetOriginal(currentUser));
}
else
throw new ValidationException(ErrorResources.NoPermissionToUpdateUser);
}
}
else
{
// the call is from MyProfile screen
// and the caller can only update themselves
if (currentUser.Name == this.ServiceContext.User.Identity.Name)
{
// Search user from database by name
User foundUser = this.ObjectContext.Users.FirstOrDefault(u => u.Name == currentUser.Name);
if (foundUser != null)
{
// generate password hash
string passwordHash = HashHelper.ComputeSaltedHash(currentUser.Password, foundUser.PasswordSalt);
if (string.Equals(passwordHash, foundUser.PasswordHash, StringComparison.Ordinal))
{
// Re-generate password hash and password salt
foundUser.PasswordSalt = HashHelper.CreateRandomSalt();
foundUser.PasswordHash = HashHelper.ComputeSaltedHash(currentUser.NewPassword, foundUser.PasswordSalt);
// set the new password question
foundUser.PasswordQuestion = currentUser.PasswordQuestion;
// re-generate passwordAnswer hash and passwordAnswer salt
foundUser.PasswordAnswerSalt = HashHelper.CreateRandomSalt();
foundUser.PasswordAnswerHash = HashHelper.ComputeSaltedHash(currentUser.PasswordAnswer, foundUser.PasswordAnswerSalt);
foundUser.FirstName = currentUser.FirstName;
foundUser.LastName = currentUser.LastName;
foundUser.Email = currentUser.Email;
// no need to reset profile for the foundUser
foundUser.ProfileReset = (byte)0;
}
else
throw new UnauthorizedAccessException(ErrorResources.PasswordDoesNotMatch);
}
else
throw new ValidationException(ErrorResources.NoPermissionToUpdateUser);
}
else
throw new ValidationException(ErrorResources.NoPermissionToUpdateUser);
}
}
public void DeleteUser(User user)
{
// check for delete user permission
if (CheckUserDeletePermission(user))
{
if ((user.EntityState == EntityState.Detached))
{
this.ObjectContext.Users.Attach(user);
}
this.ObjectContext.Users.DeleteObject(user);
}
else
throw new ValidationException(ErrorResources.NoPermissionToDeleteUser);
}
#region "Private Methods"
private User GetUserByName(string userName)
{
return this.ObjectContext.Users.FirstOrDefault(u => u.Name == userName);
}
private bool IsAdminUser(User user)
{
return (user.UserType == "A");
}
private bool IssueIsReadOnly(Issue currentIssue)
{
// Admin user can read/update any issue
if (IsAdminUser(GetUserByName(this.ServiceContext.User.Identity.Name)))
return false;
// normal user can only read/update issues assigned to them
// or issues created by them and have not assigned to anyone.
if (currentIssue.AssignedToID != null
&& currentIssue.AssignedToID == this.ServiceContext.User.Identity.Name)
return false;
else if (currentIssue.AssignedToID == null
&& currentIssue.OpenedByID == this.ServiceContext.User.Identity.Name)
return false;
return true;
}
private bool CheckUserInsertPermission(User user)
{
// cannot add itself
if (user.Name == this.ServiceContext.User.Identity.Name)
return false;
// only admin user can insert a new user
if (IsAdminUser(GetUserByName(this.ServiceContext.User.Identity.Name)))
return true;
else
return false;
}
private bool CheckUserDeletePermission(User user)
{
// cannot delete itself
if (user.Name == this.ServiceContext.User.Identity.Name)
return false;
// only admin user can delete a user
if (IsAdminUser(GetUserByName(this.ServiceContext.User.Identity.Name)))
return true;
else
return false;
}
#endregion "Private Methods"
}
}