Yi EungJun 2014-04-11
Merge branch 'master' into feature/organization-working to sync
Conflicts:
	app/utils/AccessControl.java
	test/controllers/IssueAppTest.java
@c02a5f7ed9f1cba58d7f40eaaacfe8dc86d942bb
app/controllers/UserApp.java
--- app/controllers/UserApp.java
+++ app/controllers/UserApp.java
@@ -86,7 +86,8 @@
             return status(Http.Status.NOT_ACCEPTABLE);
         }
 
-        ExpressionList<User> el = User.find.select("loginId, name").where().disjunction();
+        ExpressionList<User> el = User.find.select("loginId, name").where()
+            .ne("state", UserState.DELETED).disjunction();
         el.icontains("loginId", query);
         el.icontains("name", query);
         el.endJunction();
app/models/Label.java
--- app/models/Label.java
+++ app/models/Label.java
@@ -49,21 +49,6 @@
     }
 
     /**
-     * 현재 인스턴스와 같은 라벨가 있는지의 여부를 반환한다.
-     *
-     * when: 사용자가 라벨를 추가하려고 했을 때, 중복 여부를 검사하고자 할 때 사용하고 있다.
-     *
-     * 이 인스턴스의 {@link Label#category}와 {@link Label#name}가 모두 같은 라벨가 DB에 존재하는지 확인한다.
-     *
-     * @return 같은 것이 존재하면 {@code true}, 아니면 {@code false}
-     */
-    @Transient
-    public boolean exists() {
-        return find.where().eq("category", category).eq("name", name)
-            .findRowCount() > 0;
-    }
-
-    /**
      * 라벨를 삭제한다.
      *
      * 모든 프로젝트에서 이 라벨를 제거한 뒤, 라벨를 삭제한다.
app/models/User.java
--- app/models/User.java
+++ app/models/User.java
@@ -216,22 +216,29 @@
     }
 
     /**
-     * email로 사용자를 조회한다.
      *
-     * 사용자가 없으면 {@link #anonymous}객체에 email을 할당하고 반환한다.
+     * Find a user by email account.
+     * - find a user with a given email account or who has the email account  as one of sub email accounts.
+     * - If no user matched up with the given email account, then return new {@link models.NullUser}
+     * after setting the email account to the object.
      *
      * @param email
      * @return
      */
     public static User findByEmail(String email) {
         User user = find.where().eq("email", email).findUnique();
-        if (user == null) {
-            anonymous.email = email;
-            return anonymous;
-        }
-        else {
+        if (user != null) {
             return user;
         }
+
+        Email subEmail = Email.findByEmail(email, true);
+        if (subEmail != null) {
+            return subEmail.user;
+        }
+
+        User anonymous = new NullUser();
+        anonymous.email = email;
+        return anonymous;
     }
 
     /**
@@ -479,6 +486,10 @@
             enrolledProjects.clear();
             notificationEvents.clear();
             for (Assignee assignee : Assignee.finder.where().eq("user.id", id).findList()) {
+                for (Issue issue : assignee.issues) {
+                    issue.assignee = null;
+                    issue.update();
+                }
                 assignee.delete();
             }
         }
@@ -572,26 +583,6 @@
     public void removeEmail(Email email) {
         emails.remove(email);
         email.delete();
-    }
-
-    /**
-     * {@code committerEmail}에 해당하는 User를 찾아 반환한다.
-     *
-     * @param committerEmail
-     * @return
-     */
-    public static User findByCommitterEmail(String committerEmail) {
-        User user = find.where().eq("email", committerEmail).findUnique();
-        if (user != null) {
-            return user;
-        }
-
-        Email email = Email.findByEmail(committerEmail, true);
-        if (email != null) {
-            return email.user;
-        }
-
-        return anonymous;
     }
 
     public void visits(Project project) {
app/playRepository/GitBranch.java
--- app/playRepository/GitBranch.java
+++ app/playRepository/GitBranch.java
@@ -44,7 +44,7 @@
         this.name = name;
         this.shortName = StringUtils.removeStart(name, Constants.R_HEADS);
         this.headCommit = headCommit;
-        this.user = User.findByCommitterEmail(headCommit.getCommitterEmail());
+        this.user = User.findByEmail(headCommit.getCommitterEmail());
     }
 
     public String getName() {
app/playRepository/GitRepository.java
--- app/playRepository/GitRepository.java
+++ app/playRepository/GitRepository.java
@@ -1150,7 +1150,7 @@
         if (personIdent == null) {
             return User.anonymous;
         }
-        return User.findByCommitterEmail(personIdent.getEmailAddress());
+        return User.findByEmail(personIdent.getEmailAddress());
     }
 
     /**
app/utils/AccessControl.java
--- app/utils/AccessControl.java
+++ app/utils/AccessControl.java
@@ -66,7 +66,7 @@
     }
 
     public static boolean isResourceCreatable(User user, Resource container, ResourceType resourceType) {
-        if (isAllowedIfAuthor(user, container)) {
+        if (isAllowedIfAuthor(user, container) || isAllowedIfAssignee(user, container)) {
             return true;
         }
 
@@ -191,7 +191,8 @@
 
         if (user.isSiteManager()
                 || ProjectUser.isManager(user.id, project.id)
-                || isAllowedIfAuthor(user, resource)) {
+                || isAllowedIfAuthor(user, resource)
+                || isAllowedIfAssignee(user, resource)) {
             return true;
         }
 
@@ -335,4 +336,26 @@
             return false;
         }
     }
+
+    /**
+     * Checks if an user has a permission to do something to the given
+     * resource as an assignee.
+     *
+     * Returns true if and only if these are all true:
+     * - {@code resource} gives permission to read, modify and delete to its assignee.
+     * - {@code user} is an assignee of the resource.
+     *
+     * @param user
+     * @param resource
+     * @return true if the user has the permission
+     */
+    private static boolean isAllowedIfAssignee(User user, Resource resource) {
+        switch (resource.getType()) {
+        case ISSUE_POST:
+            Assignee assignee = Issue.finder.byId(Long.valueOf(resource.getId())).assignee;
+            return assignee != null && assignee.user.id.equals(user.id);
+        default:
+            return false;
+        }
+    }
 }
conf/evolutions/default/72.sql
--- conf/evolutions/default/72.sql
+++ conf/evolutions/default/72.sql
@@ -148,7 +148,7 @@
 WHERE comment_thread.id IN (
     SELECT t.id
     FROM comment_thread t, pull_request pr
-    WHERE t.pull_request_id = pr.id AND pr.state IN (2, 3));
+    WHERE t.pull_request_id = pr.id AND pr.state IN (2, 3, 6));
 
 DROP TABLE IF EXISTS pull_request_comment;
 
docs/technical/access-control.md
--- docs/technical/access-control.md
+++ docs/technical/access-control.md
@@ -4,6 +4,8 @@
 * 사이트 관리자: 모든 권한
 * 저자: 자신이 만든 이슈, 게시물, 댓글에 대한 모든 권한
     * 다만 자신이 만든 이슈나 게시물에 다른 사람이 댓글을 단 경우, 그 댓글에 대한 수정/삭제 권한은 갖지 않는다.
+* 담당자: 자신이 담당한 이슈, 게시물, 댓글에 대한 모든 권한
+    * 다만 자신이 담당한 이슈나 게시물에 다른 사람이 댓글을 단 경우, 그 댓글에 대한 수정/삭제 권한은 갖지 않는다.
 
 공개 프로젝트
 =============
test/controllers/IssueAppTest.java
--- test/controllers/IssueAppTest.java
+++ test/controllers/IssueAppTest.java
@@ -24,6 +24,7 @@
     private User manager;
     private User member;
     private User author;
+    private User assignee;
     private User nonmember;
     private User anonymous;
     private Issue issue;
@@ -51,6 +52,7 @@
         manager = User.findByLoginId("yobi");
         member = User.findByLoginId("laziel");
         author = User.findByLoginId("nori");
+        assignee = User.findByLoginId("alecsiel");
         nonmember = User.findByLoginId("doortts");
         anonymous = new NullUser();
 
@@ -59,6 +61,7 @@
         issue.setTitle("hello");
         issue.setBody("world");
         issue.setAuthor(author);
+        issue.setAssignee(Assignee.add(assignee.id, project.id));
         issue.save();
 
         assertThat(this.admin.isSiteManager()).describedAs("admin is Site Admin.").isTrue();
@@ -66,7 +69,8 @@
         assertThat(ProjectUser.isManager(member.id, project.id)).describedAs("member is a manager").isFalse();
         assertThat(ProjectUser.isMember(member.id, project.id)).describedAs("member is a member").isTrue();
         assertThat(ProjectUser.isMember(author.id, project.id)).describedAs("author is a member").isFalse();
-        assertThat(project.projectScope).describedAs("project is public").isNotEqualTo(ProjectScope.PUBLIC);
+        assertThat(project.isPublic).describedAs("project is public").isFalse();
+        assertThat(ProjectUser.isMember(assignee.id, project.id)).describedAs("assignee is a member").isFalse();
     }
 
     @After
@@ -152,6 +156,14 @@
         assertThat(status(result)).describedAs("Author can edit own issue.").isEqualTo(SEE_OTHER);
     }
 
+    @Test
+    public void editByAssignee() {
+        // When
+        Result result = editBy(assignee);
+
+        // Then
+        assertThat(status(result)).describedAs("Assignee can edit own issue.").isEqualTo(SEE_OTHER);
+    }
 
     @Test
     public void editByAdmin() {
@@ -203,6 +215,14 @@
         assertThat(status(result)).describedAs("Author can delete own issue.").isEqualTo(SEE_OTHER);
     }
 
+    @Test
+    public void deleteByAssignee() {
+        // When
+        Result result = deleteBy(assignee);
+
+        // Then
+        assertThat(status(result)).describedAs("Assignee can delete own issue.").isEqualTo(SEE_OTHER);
+    }
 
     @Test
     public void deleteByAdmin() {
@@ -383,6 +403,23 @@
     }
 
     @Test
+    public void watchByAssignee() {
+        // Given
+        Resource resource = issue.asResource();
+
+        // When
+        Result result = callAction(
+                controllers.routes.ref.WatchApp.watch(resource.asParameter()),
+                fakeRequest()
+                        .withSession(UserApp.SESSION_USERID, assignee.id.toString())
+        );
+
+        // Then
+        issue.refresh();
+        assertThat(status(result)).isEqualTo(OK);
+    }
+
+    @Test
     public void unwatch() {
         // Given
         Resource resource = issue.asResource();
@@ -420,4 +457,22 @@
         issue.refresh();
         assertThat(status(result)).isEqualTo(OK);
     }
+
+    @Test
+    public void unwatchByAssignee() {
+        // Given
+        Resource resource = issue.asResource();
+
+        // When
+        Result result = callAction(
+                controllers.routes.ref.WatchApp.unwatch(resource.asParameter()),
+                fakeRequest()
+                        .withSession(UserApp.SESSION_USERID, assignee.id.toString())
+        );
+
+        // Then
+        issue.refresh();
+        assertThat(status(result)).isEqualTo(OK);
+    }
+
 }
test/models/UserTest.java
--- test/models/UserTest.java
+++ test/models/UserTest.java
@@ -158,4 +158,24 @@
         assertThat(project.getWatchingCount()).isEqualTo(0);
     }
 
+    @Test
+    public void changeState() {
+        // Given
+        User user = new User();
+        user.loginId = "foo";
+        user.save();
+        Project project = new Project();
+        project.save();
+        Issue issue = new Issue();
+        issue.project = project;
+        issue.assignee = new Assignee(user.id, project.id);
+        issue.save();
+
+        // When
+        user.changeState(UserState.DELETED);
+
+        // Then
+        issue.refresh();
+        assertThat(issue.assignee).isNull();
+    }
 }
Add a comment
List