김정국 2014-03-26
Merge branch 'refectoring/actions' of whiteship/yobi
from pull request 727
@0978c4354a4d389aa9186c5be6ffc8b5f53906b1
app/actions/AbstractProjectCheckAction.java
--- app/actions/AbstractProjectCheckAction.java
+++ app/actions/AbstractProjectCheckAction.java
@@ -37,10 +37,9 @@
 
 /**
  * /{user.loginId}/{project.name}/** 패턴의 요청에 해당하는 프로젝트가 존재하는지 확인하는 액션.
- * - URL에 해당하는 프로젝트가 없을 때 404 Not Found 로 응답한다.
- * - 현재 사용자가 URL에 해당하는 프로젝트에 읽기 권한이 없을 경우 404 Not Found 로 응답한다.
+ * - URL에 해당하는 프로젝트가 없거나 현재 사용자가 읽기 권한이 없을 때 403 Forbidden으로 응답한다.
  * - URL에 해당하는 프로젝트가 있을 때 {@link AbstractProjectCheckAction#call(Project, Context)} 을
- *   호출하여 이후 검증 과정을 수행한다.
+ *   호출하여 이후에 추가 검증 과정을 수행한다.
  *
  * @author Keesun Baik, kjkmadness
  */
app/actions/AnonymousCheckAction.java
--- app/actions/AnonymousCheckAction.java
+++ app/actions/AnonymousCheckAction.java
@@ -30,6 +30,7 @@
 import utils.Constants;
 
 /**
+ * {@link actions.AbstractProjectCheckAction}의 추가작업으로,
  * 현재 사용자가 anonymouse이면 로그인 페이지로 리다이렉트 한다.
  *
  * @author Wansoon Park, Keesun Beak
 
app/actions/DefaultProjectCheckAction.java (added)
+++ app/actions/DefaultProjectCheckAction.java
@@ -0,0 +1,39 @@
+/**
+ * Yobi, Project Hosting SW
+ *
+ * Copyright 2013 NAVER Corp.
+ * http://yobi.io
+ *
+ * @Author Keesun Baik
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package actions;
+
+import actions.support.PathParser;
+import models.Project;
+import play.mvc.Http.Context;
+import play.mvc.Result;
+
+/**
+ * {@link AbstractProjectCheckAction}에서 제공하는 기능만을 실행하는 액션.
+ *
+ * @see {@link AbstractProjectCheckAction}
+ * @author Keesun Baik
+ */
+public class DefaultProjectCheckAction extends AbstractProjectCheckAction<Void> {
+    @Override
+    protected Result call(Project project, Context context, PathParser parser) throws Throwable {
+        return this.delegate.call(context);
+    }
+}
app/actions/IsAllowedAction.java
--- app/actions/IsAllowedAction.java
+++ app/actions/IsAllowedAction.java
@@ -35,6 +35,7 @@
 import utils.ErrorViews;
 
 /**
+ * {@link actions.AbstractProjectCheckAction}의 추가작업으로,
  * 1. 특정 타입의 리소스가 존재하는지 확인한다.
  * 2. 특정 타입의 리소스에 권한이 있는지 확인한다.
  *
app/actions/IsCreatableAction.java
--- app/actions/IsCreatableAction.java
+++ app/actions/IsCreatableAction.java
@@ -32,6 +32,7 @@
 import controllers.annotation.IsCreatable;
 
 /**
+ * {@link actions.AbstractProjectCheckAction}의 추가작업으로,
  * 프로젝트에 특정 타입의 리소스를 생성할 수 있는지 확인한다.
  *
  * @author Wansoon Park, Keesun Baik
app/actions/IsOnlyGitAvailableAction.java
--- app/actions/IsOnlyGitAvailableAction.java
+++ app/actions/IsOnlyGitAvailableAction.java
@@ -29,6 +29,7 @@
 import utils.ErrorViews;
 
 /**
+ * {@link actions.AbstractProjectCheckAction}의 추가작업으로,
  * 프로젝트가 Git 프로젝트인지 확인한다.
  *
  * @see {@link AbstractProjectCheckAction}
app/actions/NullProjectCheckAction.java
--- app/actions/NullProjectCheckAction.java
+++ app/actions/NullProjectCheckAction.java
@@ -4,7 +4,7 @@
  * Copyright 2013 NAVER Corp.
  * http://yobi.io
  *
- * @Author Keesun Baik
+ * @Author Wansoon Park, Keesun Baek
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -21,21 +21,44 @@
 package actions;
 
 import actions.support.PathParser;
+import controllers.UserApp;
 import models.Project;
-import play.mvc.Http.Context;
+import models.User;
+import play.i18n.Messages;
+import play.mvc.Action;
+import play.mvc.Http;
 import play.mvc.Result;
+import utils.AccessLogger;
+import utils.ErrorViews;
+
+import static play.mvc.Controller.flash;
 
 /**
  * /{user.loginId}/{project.name}/** 패턴의 요청에 해당하는 프로젝트가 존재하는지 확인하는 액션.
- * - URL에 해당하는 프로젝트가 없을 때 404 Not Found로 응답한다.
- * - URL에 해당하는 프로젝트가 있을 때 요청 처리한다.
+ * - URL에 해당하는 프로젝트가 없을 때 403 Forbidden으로 응답한다.
  *
- * @see {@link AbstractProjectCheckAction}
- * @author Keesun Baik
+ * @author Keeun Baik
  */
-public class NullProjectCheckAction extends AbstractProjectCheckAction<Void> {
+public class NullProjectCheckAction extends Action<Void> {
+
     @Override
-    protected Result call(Project project, Context context, PathParser parser) throws Throwable {
+    public Result call(Http.Context context) throws Throwable {
+        PathParser parser = new PathParser(context);
+        String ownerLoginId = parser.getOwnerLoginId();
+        String projectName = parser.getProjectName();
+
+        Project project = Project.findByOwnerAndProjectName(ownerLoginId, projectName);
+
+        if (project == null) {
+            if (UserApp.currentUser() == User.anonymous){
+                flash("failed", Messages.get("error.auth.unauthorized.waringMessage"));
+                return AccessLogger.log(context.request(),
+                        forbidden(ErrorViews.Forbidden.render("error.forbidden.or.notfound", context.request().path())), null);
+            }
+            return AccessLogger.log(context.request(),
+                    forbidden(ErrorViews.NotFound.render("error.forbidden.or.notfound")), null);
+        }
+
         return this.delegate.call(context);
     }
 }
app/controllers/BoardApp.java
--- app/controllers/BoardApp.java
+++ app/controllers/BoardApp.java
@@ -2,7 +2,7 @@
 package controllers;
 
 import actions.AnonymousCheckAction;
-import actions.NullProjectCheckAction;
+import actions.DefaultProjectCheckAction;
 
 import com.avaje.ebean.ExpressionList;
 import com.avaje.ebean.Page;
@@ -220,7 +220,7 @@
      * @see AbstractPostingApp#editPosting(models.AbstractPosting, models.AbstractPosting, play.data.Form
      */
     @Transactional
-    @With(NullProjectCheckAction.class)
+    @With(DefaultProjectCheckAction.class)
     public static Result editPost(String userName, String projectName, Long number) {
         Form<Posting> postForm = new Form<>(Posting.class).bindFromRequest();
         Project project = ProjectApp.getProject(userName, projectName);
@@ -321,7 +321,7 @@
      * @see controllers.AbstractPostingApp#delete(play.db.ebean.Model, models.resource.Resource, play.mvc.Call)
      */
     @Transactional
-    @With(NullProjectCheckAction.class)
+    @With(DefaultProjectCheckAction.class)
     public static Result deleteComment(String userName, String projectName, Long number, Long commentId) {
         Comment comment = PostingComment.find.byId(commentId);
         Project project = comment.asResource().getProject();
app/controllers/CodeApp.java
--- app/controllers/CodeApp.java
+++ app/controllers/CodeApp.java
@@ -1,6 +1,6 @@
 package controllers;
 
-import actions.NullProjectCheckAction;
+import actions.DefaultProjectCheckAction;
 import controllers.annotation.IsAllowed;
 import models.Project;
 import models.enumeration.Operation;
@@ -21,7 +21,6 @@
 
 import javax.servlet.ServletException;
 import java.io.IOException;
-import java.io.InputStream;
 import java.net.URLEncoder;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -75,7 +74,7 @@
      * @param branch 브랜치 이름
      * @param path 파일 경로
      */
-    @With(NullProjectCheckAction.class)
+    @With(DefaultProjectCheckAction.class)
     public static Result codeBrowserWithBranch(String userName, String projectName, String branch, String path)
         throws UnsupportedOperationException, IOException, SVNException, GitAPIException, ServletException {
         Project project = ProjectApp.getProject(userName, projectName);
@@ -110,7 +109,7 @@
      * @param projectName 프로젝트 이름
      * @param path 파일 또는 폴더의 경로
      */
-    @With(NullProjectCheckAction.class)
+    @With(DefaultProjectCheckAction.class)
     public static Result ajaxRequest(String userName, String projectName, String path) throws Exception{
         PlayRepository repository = RepositoryService.getRepository(userName, projectName);
         ObjectNode fileInfo = repository.getMetaDataFromPath(path);
@@ -130,7 +129,7 @@
      * @param branch 브랜치 이름
      * @param path 파일 또는 폴더의 경로
      */
-    @With(NullProjectCheckAction.class)
+    @With(DefaultProjectCheckAction.class)
     public static Result ajaxRequestWithBranch(String userName, String projectName, String branch, String path)
             throws UnsupportedOperationException, IOException, SVNException, GitAPIException, ServletException{
         CodeApp.hostName = request().host();
@@ -152,7 +151,7 @@
      * @param revision
      * @param path
      */
-    @With(NullProjectCheckAction.class)
+    @With(DefaultProjectCheckAction.class)
     public static Result showRawFile(String userName, String projectName, String revision, String path) throws Exception{
         byte[] fileAsRaw = RepositoryService.getFileAsRaw(userName, projectName, revision, path);
         if(fileAsRaw == null){
@@ -168,7 +167,7 @@
      * @param projectName
      * @param path
      */
-    @With(NullProjectCheckAction.class)
+    @With(DefaultProjectCheckAction.class)
     public static Result showImageFile(String userName, String projectName, String revision, String path) throws Exception{
         final byte[] fileAsRaw = RepositoryService.getFileAsRaw(userName, projectName, revision, path);
         String mimeType = tika.detect(fileAsRaw);
app/controllers/CodeHistoryApp.java
--- app/controllers/CodeHistoryApp.java
+++ app/controllers/CodeHistoryApp.java
@@ -1,6 +1,6 @@
 package controllers;
 
-import actions.NullProjectCheckAction;
+import actions.DefaultProjectCheckAction;
 import controllers.annotation.IsAllowed;
 import controllers.annotation.IsCreatable;
 import models.Attachment;
@@ -59,7 +59,7 @@
      * @throws GitAPIException
      * @throws SVNException
      */
-    @With(NullProjectCheckAction.class)
+    @With(DefaultProjectCheckAction.class)
     public static Result historyUntilHead(String ownerName, String projectName) throws IOException,
             UnsupportedOperationException, ServletException, GitAPIException,
             SVNException {
@@ -218,7 +218,7 @@
         return toView;
     }
 
-    @With(NullProjectCheckAction.class)
+    @With(DefaultProjectCheckAction.class)
     @IsAllowed(value = Operation.DELETE, resourceType = ResourceType.COMMIT_COMMENT)
     public static Result deleteComment(String ownerName, String projectName, String commitId,
                                        Long id) {
app/controllers/EnrollProjectApp.java
--- app/controllers/EnrollProjectApp.java
+++ app/controllers/EnrollProjectApp.java
@@ -1,7 +1,7 @@
 package controllers;
 
 import actions.AnonymousCheckAction;
-import actions.NullProjectCheckAction;
+import actions.DefaultProjectCheckAction;
 import models.NotificationEvent;
 import models.Project;
 import models.ProjectUser;
@@ -27,7 +27,7 @@
      * @return
      */
     @Transactional
-    @With(NullProjectCheckAction.class)
+    @With(DefaultProjectCheckAction.class)
     public static Result enroll(String loginId, String projectName) {
         Project project = Project.findByOwnerAndProjectName(loginId, projectName);
 
@@ -53,7 +53,7 @@
      * @return
      */
     @Transactional
-    @With(NullProjectCheckAction.class)
+    @With(DefaultProjectCheckAction.class)
     public static Result cancelEnroll(String loginId, String proejctName) {
         Project project = Project.findByOwnerAndProjectName(loginId, proejctName);
 
app/controllers/IssueApp.java
--- app/controllers/IssueApp.java
+++ app/controllers/IssueApp.java
@@ -1,6 +1,6 @@
 package controllers;
 
-import actions.NullProjectCheckAction;
+import actions.DefaultProjectCheckAction;
 import actions.AnonymousCheckAction;
 import com.avaje.ebean.ExpressionList;
 import com.avaje.ebean.Page;
@@ -278,7 +278,7 @@
      * @param number 이슈 번호
      * @return
      */
-    @With(NullProjectCheckAction.class)
+    @With(DefaultProjectCheckAction.class)
     public static Result issue(String ownerName, String projectName, Long number) {
         Project project = ProjectApp.getProject(ownerName, projectName);
 
@@ -380,7 +380,7 @@
      * @throws IOException
      */
     @Transactional
-    @With(NullProjectCheckAction.class)
+    @With(DefaultProjectCheckAction.class)
     public static Result massUpdate(String ownerName, String projectName) {
         Form<IssueMassUpdate> issueMassUpdateForm
                 = new Form<>(IssueMassUpdate.class).bindFromRequest();
@@ -642,7 +642,7 @@
      * @throws IOException
      * @see {@link AbstractPostingApp#editPosting}
      */
-    @With(NullProjectCheckAction.class)
+    @With(DefaultProjectCheckAction.class)
     public static Result editIssue(String ownerName, String projectName, Long number) {
         Form<Issue> issueForm = new Form<>(Issue.class).bindFromRequest();
 
@@ -842,7 +842,7 @@
      * @see {@link AbstractPostingApp#delete(play.db.ebean.Model, models.resource.Resource, Call)}
      */
     @Transactional
-    @With(NullProjectCheckAction.class)
+    @With(DefaultProjectCheckAction.class)
     public static Result deleteComment(String ownerName, String projectName, Long issueNumber,
             Long commentId) {
         Comment comment = IssueComment.find.byId(commentId);
app/controllers/ProjectApp.java
--- app/controllers/ProjectApp.java
+++ app/controllers/ProjectApp.java
@@ -1,7 +1,7 @@
 package controllers;
 
 import actions.AnonymousCheckAction;
-import actions.NullProjectCheckAction;
+import actions.DefaultProjectCheckAction;
 
 import com.avaje.ebean.ExpressionList;
 import com.avaje.ebean.Junction;
@@ -655,7 +655,7 @@
      * @return 프로젝트, 멤버목록, Role 목록
      */
     @Transactional
-    @With(NullProjectCheckAction.class)
+    @With(DefaultProjectCheckAction.class)
     public static Result newMember(String loginId, String projectName) {
         // TODO change into view validation
         Form<User> addMemberForm = form(User.class).bindFromRequest();
@@ -712,7 +712,7 @@
      * @return the result
      */
     @Transactional
-    @With(NullProjectCheckAction.class)
+    @With(DefaultProjectCheckAction.class)
     public static Result deleteMember(String loginId, String projectName, Long userId) {
         Project project = Project.findByOwnerAndProjectName(loginId, projectName);
 
@@ -925,7 +925,7 @@
      * @return the result
      */
     @Transactional
-    @With(NullProjectCheckAction.class)
+    @With(DefaultProjectCheckAction.class)
     public static Result attachLabel(String ownerName, String projectName) {
         Project project = Project.findByOwnerAndProjectName(ownerName, projectName);
 
@@ -996,7 +996,7 @@
      * @return the result
      */
     @Transactional
-    @With(NullProjectCheckAction.class)
+    @With(DefaultProjectCheckAction.class)
     public static Result detachLabel(String ownerName, String projectName, Long id) {
         Project project = Project.findByOwnerAndProjectName(ownerName, projectName);
 
app/controllers/StatisticsApp.java
--- app/controllers/StatisticsApp.java
+++ app/controllers/StatisticsApp.java
@@ -1,6 +1,6 @@
 package controllers;
 
-import actions.NullProjectCheckAction;
+import actions.DefaultProjectCheckAction;
 import models.Project;
 import play.mvc.Controller;
 import play.mvc.Result;
@@ -9,7 +9,7 @@
 
 public class StatisticsApp extends Controller {
 
-    @With(NullProjectCheckAction.class)
+    @With(DefaultProjectCheckAction.class)
     public static Result statistics(String userName, String projectName) {
         Project project = Project.findByOwnerAndProjectName(userName, projectName);
         return ok(statistics.render("statistics", project));
docs/technical/validation-with-annotation.md
--- docs/technical/validation-with-annotation.md
+++ docs/technical/validation-with-annotation.md
@@ -11,6 +11,7 @@
 * `@IsAllowed`
 * `@With(AnonymousCheckAction.class)`
 * `@With(NullProjectCheckAction.class)`
+* `@With(DefaultProjectCheckAction.class)`
 
 Yobi에서 사용하는 인증 및 권한 검사 애노테이션은 다음과 같은 URL 패턴에만 적용할 수 있다.
 
@@ -18,12 +19,29 @@
 
 `conf/application.conf`에 설정한 application.context가 있을 경우 해당 컨텍스트 패스를 제거한 URL 패턴에 적용된다.
 
+## @With(DefaultProjectCheckAction.class)
+
+이 애노테이션은 URL 패턴에 해당하는 프로젝트가 있는지 그리고 해당 프로젝트를 볼 수 있는 권한이 있는지 확인한다.
+
+URL에 해당하는 프로젝트가 없거나 현재 사용자가 읽기 권한이 없을 때 403 Forbidden으로 응답한다.
+
+### 사용예
+
+다음 코드는 URL 패턴에 해당하는 프로젝트가 있는지 확인한다.
+
+```
+@With(DefaultProjectCheckAction.class)
+public static Result editIssue(String ownerName, String projectName, Long number) {
+    // 코드 생략
+}
+```
+
 ## @IsOnlyGitAvailable
 
-GIT 저장소를 사용하는 프로젝트인지 확인한다.
+`@With(DefaultProjectCheckAction.class)`가 확인하는 작업에 추가로 GIT 저장소를 사용하는 프로젝트인지 확인한다.
 
-URL 패턴에 해당하는 프로젝트가 없는 경우 응답으로 404 Not Found를 반환한다.
-요청 핸들러에 이 애노테이션을 사용하면 GIT 저장소를 사용하지 않는 프로젝트는 응답으로 400 Bad Request를 반환한다.
+URL에 해당하는 프로젝트가 없거나 현재 사용자가 읽기 권한이 없을 때 403 Forbidden으로 응답한다.
+URL에 해당하는 프로젝트가 GIT 저장소를 사용하지 않는 프로젝트일 경우에는 응답으로 400 Bad Request를 반환한다.
 
 컨트롤러 클래스에 이 애노테이션을 사용하면 해당 컨트롤러의 모든 메서드에 적용된다.
 
@@ -40,10 +58,12 @@
 
 ## @IsCreatable
 
-프로젝트에 특정 리소스 타입을 생성할 수 있는지 확인한다.
+`@With(DefaultProjectCheckAction.class)`가 확인하는 작업에 추가로 현재 사용자가 프로젝트에 특정 리소스 타입을 생성할 수 있는지 확인한다.
 
-URL 패턴에 해당하는 프로젝트가 없는 경우 응답으로 404 Not Found를 반환한다.
-프로젝트에 해당 리소스를 생성할 권한이 없다면 응답으로 403 Forbidden을 반환한다.
+다음의 경우 403 Forbidden으로 응답한다.
+* URL에 해당하는 프로젝트가 없다.
+* URL에 해당하는 프로젝트에 현재 사용자가 읽기 권한이 없다.
+* URL에 해당하는 프로젝트에 해당 리소스를 생성할 권한이 없다.
 
 ### 파라미터
 
@@ -62,10 +82,12 @@
 
 ## @IsAllowed
 
-특정 리소스에 특정 권한이 가능한지 확인한다.
+`@With(DefaultProjectCheckAction.class)`가 확인하는 작업에 추가로 현재 사용자가 특정 리소스에 특정 권한이 가능한지 확인한다.
 
-URL 패턴에 해당하는 프로젝트나 리소스가 없는 경우 응답으로 404 Not Found를 반환한다.
-특정 리소스(`ResourceType`)에 특정 동작(`Operation`)이 가능하지 않을 경우 응답으로 403 Forbidden을 반환한다.
+다음의 경우 403 Forbidden으로 응답한다.
+* URL에 해당하는 프로젝트가 없다.
+* URL에 해당하는 프로젝트에 현재 사용자가 읽기 권한이 없다.
+* 현재 사용자에게 특정 리소스(`ResourceType`)에 특정 동작(`Operation`)을 허용하지 않는다.
 
 ### 파라미터
 
@@ -89,7 +111,6 @@
 이밖에 필요한 리소스 타입이 있다면 `Resource.getResourceObject` 메서드에 코드를 추가해야 한다.
 
 ### 사용예
-
 
 다음 코드는 resourceType을 명시하지 않았기 때문에 기본값인 PROJECT가 적용되어 현재 사용자가 PROJECT 리소스 타입에 READ 권한이 있는지 확인한다.
 
@@ -130,7 +151,7 @@
 
 이 애노테이션은 URL 패턴에 해당하는 프로젝트가 있는지 확인한다.
 
-URL 패턴에 해당하는 프로젝트가 없는 경우 응답으로 404 Not Found를 반환한다.
+URL에 해당하는 프로젝트가 없을 때 403 Forbidden으로 응답한다.
 
 ### 사용예
 
Add a comment
List