[Notice] Announcing the End of Demo Server [Read me]
Yi EungJun 2013-01-18
Rewrite Access Controller
* Simplify AccessControl's interface.
    * Reduce isAllowed's parameters 5 to 3.
    * Add Add isCreatable() instead of Operation.CREATE
    * Reanme Operation.EDIT to Opeartion.UPDATE
* Remove unnecessary Permission entity.
* Add SiteAdmin entity to classify who is Site Admin.
@b7f4fafe2fa83ebefac7095cf044158483c75602
 
RUNNING_PID (deleted)
--- RUNNING_PID
@@ -1,1 +0,0 @@
-5728(No newline at end of file)
app/Global.java
--- app/Global.java
+++ app/Global.java
@@ -30,12 +30,8 @@
             Ebean.save(all.get("issueComments"));
             Ebean.save(all.get("posts"));
             Ebean.save(all.get("comments"));
-            Ebean.save(all.get("permissions"));
 
             Ebean.save(all.get("roles"));
-            for (Object role : all.get("roles")) {
-                Ebean.saveManyToManyAssociations(role, "permissions");
-            }
             Ebean.save(all.get("projectUsers"));
 
             Ebean.save(all.get("taskBoards"));
@@ -43,6 +39,8 @@
             Ebean.save(all.get("cards"));
             Ebean.save(all.get("labels"));
             Ebean.save(all.get("checkLists"));
+
+            Ebean.save(all.get("siteAdmins"));
         }
     }
     
app/controllers/AttachmentApp.java
--- app/controllers/AttachmentApp.java
+++ app/controllers/AttachmentApp.java
@@ -11,6 +11,7 @@
 import java.util.Map;
 
 import models.Attachment;
+import models.Project;
 import models.enumeration.Operation;
 import models.enumeration.Resource;
 
@@ -99,8 +100,7 @@
             return notFound();
         }
 
-        if (!AccessControl.isAllowed(UserApp.currentUser().id, attachment.projectId,
-                attachment.containerType, Operation.READ, attachment.containerId)) {
+        if (!AccessControl.isAllowed(UserApp.currentUser(), attachment.getContainerAsResource(), Operation.READ)) {
             return forbidden();
         }
 
@@ -131,8 +131,7 @@
             return notFound();
         }
 
-        if (!AccessControl.isAllowed(UserApp.currentUser().id, attach.projectId,
-                attach.containerType, Operation.DELETE, attach.containerId)) {
+        if (!AccessControl.isAllowed(UserApp.currentUser(), attach.getContainerAsResource(), Operation.DELETE)) {
             return forbidden();
         }
 
@@ -188,8 +187,8 @@
             List<Map<String, String>> attachments = new ArrayList<Map<String, String>>();
             for (Attachment attach : Attachment.findByContainer(Resource.valueOf(containerType),
                     Long.parseLong(containerId))) {
-                if (!AccessControl.isAllowed(UserApp.currentUser().id, attach.projectId,
-                        attach.containerType, Operation.READ, attach.containerId)) {
+                if (!AccessControl.isAllowed(UserApp.currentUser(),
+                        attach.getContainerAsResource(), Operation.READ)) {
                     return forbidden();
                 }
                 attachments.add(fileAsMap(attach));
app/controllers/BoardApp.java
--- app/controllers/BoardApp.java
+++ app/controllers/BoardApp.java
@@ -10,6 +10,7 @@
 import models.Comment;
 import models.Post;
 import models.Project;
+import models.User;
 import models.enumeration.Direction;
 import models.enumeration.Operation;
 import models.enumeration.Resource;
@@ -27,14 +28,14 @@
 import views.html.board.postList;
 
 public class BoardApp extends Controller {
-    
+
     //TODO 이 클래스는 원래 따로 존재해야 함.
     public static class SearchCondition{
         public final static String ORDERING_KEY_ID = "id";
         public final static String ORDERING_KEY_TITLE = "title";
         public final static String ORDERING_KEY_AGE = "date";
         public final static String ORDERING_KEY_AUTHOR = "authorName";
-        
+
         public SearchCondition() {
             this.order = Direction.DESC.direction();
             this.key = ORDERING_KEY_ID;
@@ -47,14 +48,14 @@
         public String filter;
         public int pageNum;
     }
-    
+
 
     public static Result posts(String userName, String projectName) {
-
         Form<SearchCondition> postParamForm = new Form<SearchCondition>(SearchCondition.class);
         SearchCondition postSearchCondition = postParamForm.bindFromRequest().get();
         Project project = ProjectApp.getProject(userName, projectName);
-        if (!AccessControl.isAllowed(session().get("userId"), project)) {
+
+        if (!AccessControl.isCreatable(User.findByLoginId(session().get("loginId")), project, Resource.BOARD_POST)) {
             return unauthorized(views.html.project.unauthorized.render(project));
         }
 
@@ -68,7 +69,7 @@
 
     public static Result newPostForm(String userName, String projectName) {
         Project project = ProjectApp.getProject(userName, projectName);
-        if (!AccessControl.isAllowed(session().get("userId"), project)) {
+        if (UserApp.currentUser() == UserApp.anonymous) {
             return unauthorized(views.html.project.unauthorized.render(project));
         }
 
@@ -102,7 +103,7 @@
     public static Result post(String userName, String projectName, Long postId) {
         Post post = Post.findById(postId);
         Project project = ProjectApp.getProject(userName, projectName);
-        if (!AccessControl.isAllowed(session().get("userId"), project)) {
+        if (!AccessControl.isCreatable(User.findByLoginId(session().get("loginId")), project, Resource.BOARD_POST)) {
             return unauthorized(views.html.project.unauthorized.render(project));
         }
 
@@ -152,8 +153,7 @@
         Form<Post> editForm = new Form<Post>(Post.class).fill(existPost);
         Project project = ProjectApp.getProject(userName, projectName);
 
-
-        if (AccessControl.isAllowed(UserApp.currentUser().id, project.id, Resource.BOARD_POST, Operation.EDIT, postId)) {
+        if (AccessControl.isAllowed(UserApp.currentUser(), existPost.asResource(), Operation.UPDATE)) {
             return ok(editPost.render("board.post.modify", editForm, postId, project));
         } else {
             flash(Constants.WARNING, "board.notAuthor");
app/controllers/CodeHistoryApp.java
--- app/controllers/CodeHistoryApp.java
+++ app/controllers/CodeHistoryApp.java
@@ -6,6 +6,7 @@
 import javax.servlet.ServletException;
 
 import models.Project;
+import models.enumeration.Operation;
 
 import org.eclipse.jgit.api.errors.GitAPIException;
 import org.eclipse.jgit.api.errors.NoHeadException;
@@ -14,6 +15,7 @@
 import play.mvc.Controller;
 import play.mvc.Result;
 import playRepository.Commit;
+import playRepository.PlayRepository;
 import playRepository.RepositoryService;
 import utils.AccessControl;
 import utils.HttpUtil;
@@ -35,7 +37,9 @@
             UnsupportedOperationException, ServletException, GitAPIException,
             SVNException {
         Project project = Project.findByNameAndOwner(userName, projectName);
-        if (!AccessControl.isAllowed(session().get("userId"), project)) {
+        PlayRepository repository = RepositoryService.getRepository(project);
+
+        if (!AccessControl.isAllowed(UserApp.currentUser(), project.asResource(), Operation.READ)) {
             return unauthorized(views.html.project.unauthorized.render(project));
         }
 
@@ -46,8 +50,7 @@
         }
 
         try {
-            List<Commit> commits = RepositoryService.getRepository(project).getHistory(page,
-                    HISTORY_ITEM_LIMIT, branch);
+            List<Commit> commits = repository.getHistory(page, HISTORY_ITEM_LIMIT, branch);
             return ok(history.render(project, commits, page, branch));
         } catch (NoHeadException e) {
             return ok(nohead.render(project));
@@ -58,7 +61,7 @@
             throws IOException, UnsupportedOperationException, ServletException, GitAPIException,
             SVNException {
         Project project = Project.findByNameAndOwner(userName, projectName);
-        if (!AccessControl.isAllowed(session().get("userId"), project)) {
+        if (!AccessControl.isAllowed(UserApp.currentUser(), project.asResource(), Operation.READ)) {
             return unauthorized(views.html.project.unauthorized.render(project));
         }
         String patch = RepositoryService.getRepository(project).getPatch(commitId);
app/controllers/GitApp.java
--- app/controllers/GitApp.java
+++ app/controllers/GitApp.java
@@ -14,6 +14,7 @@
 import play.mvc.Controller;
 import play.mvc.Result;
 import play.mvc.With;
+import playRepository.PlayRepository;
 import playRepository.RepositoryService;
 import utils.AccessControl;
 import utils.BasicAuthAction;
@@ -26,16 +27,17 @@
                 && (service.equals("git-upload-pack") || service.equals("git-receive-pack"));
     }
 
-    public static boolean isAllowed(String userName, String projectName, String service) {
+    public static boolean isAllowed(String userName, String projectName, String service) throws UnsupportedOperationException, IOException, ServletException {
         Project project = ProjectApp.getProject(userName, projectName);
 
-        Operation operation = Operation.WRITE;
+        Operation operation = Operation.UPDATE;
         if (service.equals("git-upload-pack")) {
             operation = Operation.READ;
         }
 
-        if (AccessControl.isAllowed(
-                UserApp.currentUser().id, project.id, Resource.CODE, operation, null)) {
+        PlayRepository repository = RepositoryService.getRepository(project);
+        if (AccessControl
+                .isAllowed(UserApp.currentUser(), repository.asResource(), operation)) {
             return true;
         }
 
app/controllers/IssueApp.java
--- app/controllers/IssueApp.java
+++ app/controllers/IssueApp.java
@@ -8,6 +8,7 @@
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 import java.util.List;
+import play.Logger;
 
 import jxl.write.WriteException;
 
@@ -18,6 +19,7 @@
 import models.IssueLabel;
 import models.Project;
 import models.enumeration.Direction;
+import models.enumeration.Operation;
 import models.enumeration.Resource;
 import models.enumeration.State;
 import models.support.FinderTemplate;
@@ -58,7 +60,7 @@
 	@Cached(key = "issues")
     public static Result issues(String userName, String projectName, String state, String format) throws WriteException, IOException {
         Project project = ProjectApp.getProject(userName, projectName);
-        if (!AccessControl.isAllowed(session().get("userId"), project)) {
+        if (!AccessControl.isAllowed(UserApp.currentUser(), project.asResource(), Operation.READ)) {
             return unauthorized(views.html.project.unauthorized.render(project));
         }
 
@@ -119,7 +121,7 @@
 
     public static Result newIssueForm(String userName, String projectName) {
         Project project = ProjectApp.getProject(userName, projectName);
-        if (!AccessControl.isAllowed(session().get("userId"), project)) {
+        if (UserApp.currentUser() == UserApp.anonymous) {
             return unauthorized(views.html.project.unauthorized.render(project));
         }
 
@@ -165,7 +167,7 @@
         Issue targetIssue = Issue.findById(id);
         Form<Issue> editForm = new Form<Issue>(Issue.class).fill(targetIssue);
         Project project = ProjectApp.getProject(userName, projectName);
-        if (!AccessControl.isAllowed(session().get("userId"), project)) {
+        if (!AccessControl.isAllowed(UserApp.currentUser(), targetIssue.asResource(), Operation.UPDATE)) {
             return unauthorized(views.html.project.unauthorized.render(project));
         }
 
@@ -250,4 +252,4 @@
         return redirect(routes.IssueApp.issue(project.owner, project.name, issueId));
     }
 
-}
(No newline at end of file)
+}
app/controllers/IssueLabelApp.java
--- app/controllers/IssueLabelApp.java
+++ app/controllers/IssueLabelApp.java
@@ -77,8 +77,7 @@
             return notFound();
         }
 
-        if (!AccessControl.isAllowed(UserApp.currentUser().id, label.project.id,
-            Resource.ISSUE_LABEL, Operation.DELETE, label.id)) {
+        if (!AccessControl.isAllowed(UserApp.currentUser(), label.asResource(), Operation.DELETE)) {
             return forbidden();
         }
 
app/controllers/ProjectApp.java
--- app/controllers/ProjectApp.java
+++ app/controllers/ProjectApp.java
@@ -6,6 +6,7 @@
 import models.ProjectUser;
 import models.Role;
 import models.User;
+import models.enumeration.Operation;
 import models.enumeration.RoleType;
 import models.task.CardAssignee;
 import play.cache.Cached;
@@ -42,7 +43,7 @@
 //    @Cached(key = "project")
     public static Result project(String userName, String projectName) {
         Project project = ProjectApp.getProject(userName, projectName);
-        if (!AccessControl.isAllowed(session().get("userId"), project)) {
+        if (!AccessControl.isAllowed(UserApp.currentUser(), project.asResource(), Operation.READ)) {
             return unauthorized(views.html.project.unauthorized.render(project));
         }
 
@@ -62,7 +63,7 @@
 
     public static Result settingForm(String userName, String projectName) {
         Project project = getProject(userName, projectName);
-        if (!AccessControl.isAllowed(session().get("userId"), project)) {
+        if (!AccessControl.isAllowed(UserApp.currentUser(), project.asResource(), Operation.UPDATE)) {
             return unauthorized(views.html.project.unauthorized.render(project));
         }
 
@@ -108,7 +109,7 @@
         Form<Project> filledUpdatedProjectForm = form(Project.class)
                 .bindFromRequest();
         Project project = filledUpdatedProjectForm.get();
-          
+
         if(!ProjectUser.isManager(UserApp.currentUser().id, project.id)){
             flash(Constants.WARNING, "project.member.isManager");
             return redirect(routes.ProjectApp.settingForm(userName, project.name));
@@ -121,7 +122,7 @@
 
         MultipartFormData body = request().body().asMultipartFormData();
         FilePart filePart = body.getFile("logoPath");
-        
+
         if (filePart != null) {
         	if(!isImageFile(filePart.getFilename())) {
         		flash(Constants.WARNING, "project.logo.alert");
@@ -154,14 +155,14 @@
     }
 
     public static boolean isImageFile(String filename) {
-    	boolean isImageFile = false;
-    	for(String suffix : LOGO_TYPE){
-    		if(filename.toLowerCase().endsWith(suffix)) 
-    			isImageFile=true;
-    		}
-    	return isImageFile;
+        boolean isImageFile = false;
+        for(String suffix : LOGO_TYPE) {
+            if(filename.toLowerCase().endsWith(suffix))
+                isImageFile=true;
+        }
+        return isImageFile;
     }
-    
+
     public static Result deleteProject(String userName, String projectName) throws Exception {
         Project project = getProject(userName, projectName);
         if (ProjectUser.isManager(UserApp.currentUser().id, project.id)) {
app/controllers/SvnApp.java
--- app/controllers/SvnApp.java
+++ app/controllers/SvnApp.java
@@ -18,6 +18,7 @@
 import play.mvc.Controller;
 import play.mvc.Result;
 import play.mvc.With;
+import playRepository.PlayRepository;
 import playRepository.RepositoryService;
 import utils.AccessControl;
 import utils.BasicAuthAction;
@@ -28,7 +29,7 @@
 
 public class SvnApp extends Controller {
     static DAVServlet davServlet;
-    
+
     @With(BasicAuthAction.class)
     @BodyParser.Of(BodyParser.Raw.class)
     public static Result serviceWithPath(String path) throws ServletException, IOException {
@@ -68,9 +69,10 @@
         User currentUser = UserApp.currentUser();
         // Check the user has a permission to access this repository.
         Project project = Project.findByNameAndOwner(userName, projectName);
-        
-        if (!AccessControl.isAllowed(currentUser.id, project.id,
-                Resource.CODE, getRequestedOperation(request().method()), null)) {
+
+        PlayRepository repository = RepositoryService.getRepository(project);
+        if (!AccessControl.isAllowed(currentUser, repository.asResource(),
+                getRequestedOperation(request().method()))) {
             if (currentUser.id == UserApp.anonymous.id) {
                 return BasicAuthAction.unauthorized(response());
             } else {
@@ -99,7 +101,7 @@
                 || DAVHandlerFactory.METHOD_REPORT.equals(method)) {
             return Operation.READ;
         } else {
-            return Operation.WRITE;
+            return Operation.UPDATE;
         }
     }
 
app/controllers/TaskApp.java
--- app/controllers/TaskApp.java
+++ app/controllers/TaskApp.java
@@ -3,6 +3,7 @@
 import java.util.Map;
 
 import models.Project;
+import models.enumeration.Operation;
 import models.task.Card;
 import models.task.Line;
 import models.task.TaskBoard;
@@ -22,7 +23,7 @@
 public class TaskApp extends Controller {
     public static Result index(String userName, String projectName) {
         Project project = ProjectApp.getProject(userName, projectName);
-        if (!AccessControl.isAllowed(session().get("userId"), project)) {
+        if (!AccessControl.isAllowed(UserApp.currentUser(), project.asResource(), Operation.READ)) {
             return unauthorized(views.html.project.unauthorized.render(project));
         }
         return ok(taskView.render(project));
@@ -30,7 +31,7 @@
 
     public static Result card(String userName, String projectName, Long cardId) {
         Project project = ProjectApp.getProject(userName, projectName);
-        if (!AccessControl.isAllowed(session().get("userId"), project)) {
+        if (!AccessControl.isAllowed(UserApp.currentUser(), project.asResource(), Operation.READ)) {
             return unauthorized(views.html.project.unauthorized.render(project));
         }
 
@@ -39,7 +40,7 @@
 
     public static Result getLabels(String userName, String projectName) {
         Project project = ProjectApp.getProject(userName, projectName);
-        if (!AccessControl.isAllowed(session().get("userId"), project)) {
+        if (!AccessControl.isAllowed(UserApp.currentUser(), project.asResource(), Operation.READ)) {
             return unauthorized(views.html.project.unauthorized.render(project));
         }
         TaskBoard taskBoard = TaskBoard.findByProject(project);
@@ -48,7 +49,7 @@
 
     public static Result getMember(String userName, String projectName) {
         Project project = ProjectApp.getProject(userName, projectName);
-        if (!AccessControl.isAllowed(session().get("userId"), project)) {
+        if (!AccessControl.isAllowed(UserApp.currentUser(), project.asResource(), Operation.READ)) {
             return unauthorized(views.html.project.unauthorized.render(project));
         }
         TaskBoard taskBoard = TaskBoard.findByProject(project);
@@ -58,7 +59,7 @@
     // TestCode 나중에 전부 웹소켓으로 바꾼다. 당연히 그걸 고려해서 짜야한다.
     public static Result cardView(String userName, String projectName) {
         Project project = ProjectApp.getProject(userName, projectName);
-        if (!AccessControl.isAllowed(session().get("userId"), project)) {
+        if (!AccessControl.isAllowed(UserApp.currentUser(), project.asResource(), Operation.READ)) {
             return unauthorized(views.html.project.unauthorized.render(project));
         }
         return ok(cardView.render(project));
@@ -100,9 +101,9 @@
     }
 
     // TestCode End
-    
+
     public static WebSocket<String> connect(String userName, String projectName) {
         return WebSocketServer.handelWebSocket(userName, projectName);
     }
 
-}
+}
(No newline at end of file)
app/models/Assignee.java
--- app/models/Assignee.java
+++ app/models/Assignee.java
@@ -8,12 +8,8 @@
 import javax.persistence.ManyToOne;
 import javax.persistence.OneToMany;
 
-import org.apache.commons.lang.builder.HashCodeBuilder;
-
-import play.Logger;
 import play.data.validation.Constraints.Required;
 import play.db.ebean.Model;
-import play.db.ebean.Model.Finder;
 
 @Entity
 public class Assignee extends Model {
@@ -37,7 +33,7 @@
     @OneToMany(mappedBy = "assignee", cascade = CascadeType.ALL)
     public Set<Issue> issues;
 
-    public static Finder<Long, Assignee> finder = new Finder<Long, Assignee>(Long.class,
+    public static Model.Finder<Long, Assignee> finder = new Finder<Long, Assignee>(Long.class,
             Assignee.class);
 
     public Assignee(Long userId, Long projectId) {
app/models/Attachment.java
--- app/models/Attachment.java
+++ app/models/Attachment.java
@@ -18,7 +18,11 @@
 import org.apache.commons.io.FileUtils;
 import org.apache.tika.Tika;
 
+import controllers.ProjectApp;
+
 import models.enumeration.Resource;
+import models.resource.GlobalResource;
+import models.resource.ProjectResource;
 
 import play.data.validation.*;
 
@@ -55,8 +59,8 @@
     public Long containerId;
 
     /**
-     * 모든 임시파일은 컨테이너 타입 Resource.USER 에 해당한다. 
-     * 
+     * 모든 임시파일은 컨테이너 타입 Resource.USER 에 해당한다.
+     *
      * @param userId
      * @return
      */
@@ -204,4 +208,23 @@
             attachment.delete();
         }
     }
+
+    public ProjectResource getContainerAsResource() {
+        return new ProjectResource() {
+            @Override
+            public Long getId() {
+                return containerId;
+            }
+
+            @Override
+            public Project getProject() {
+                return Project.find.byId(projectId);
+            }
+
+            @Override
+            public Resource getType() {
+                return containerType;
+            }
+        };
+    }
 }
(No newline at end of file)
app/models/Comment.java
--- app/models/Comment.java
+++ app/models/Comment.java
@@ -1,5 +1,8 @@
 package models;
 
+import models.enumeration.Resource;
+import models.resource.ProjectResource;
+
 import org.joda.time.*;
 import play.data.validation.*;
 import play.db.ebean.*;
@@ -59,4 +62,23 @@
     public static void delete(Long commentId) {
         find.byId(commentId).delete();
     }
+
+    public ProjectResource asResource() {
+        return new ProjectResource() {
+            @Override
+            public Long getId() {
+                return id;
+            }
+
+            @Override
+            public Project getProject() {
+                return post.project;
+            }
+
+            @Override
+            public Resource getType() {
+                return Resource.BOARD_COMMENT;
+            }
+        };
+    }
 }
app/models/Issue.java
--- app/models/Issue.java
+++ app/models/Issue.java
@@ -10,6 +10,7 @@
 import jxl.format.Alignment;
 import jxl.write.*;
 import models.enumeration.*;
+import models.resource.ProjectResource;
 import models.support.*;
 import org.joda.time.*;
 import play.data.format.*;
@@ -493,4 +494,54 @@
 	public boolean isClosed() {
 	    return this.state == State.CLOSED;
 	}
-}
+
+	public ProjectResource asResource() {
+	    return new ProjectResource() {
+	        @Override
+	        public Long getId() {
+	            return null;
+	        }
+
+	        @Override
+	        public Project getProject() {
+	            return project;
+	        }
+
+	        @Override
+	        public Resource getType() {
+	            return Resource.ISSUE_POST;
+	        }
+	    };
+	}
+
+    public ProjectResource fieldAsResource(final Resource resourceType) {
+        return new ProjectResource() {
+            @Override
+            public Long getId() {
+                return id;
+            }
+
+            @Override
+            public Project getProject() {
+                return project;
+            }
+
+            @Override
+            public Resource getType() {
+                return resourceType;
+            }
+        };
+    }
+
+    public ProjectResource stateAsResource() {
+        return fieldAsResource(Resource.ISSUE_STATE);
+    }
+
+    public ProjectResource milestoneAsResource() {
+        return fieldAsResource(Resource.ISSUE_MILESTONE);
+    }
+
+    public ProjectResource assigneeAsResource() {
+        return fieldAsResource(Resource.ISSUE_ASSIGNEE);
+    }
+}
(No newline at end of file)
app/models/IssueComment.java
--- app/models/IssueComment.java
+++ app/models/IssueComment.java
@@ -4,6 +4,9 @@
 
 package models;
 
+import models.enumeration.Resource;
+import models.resource.ProjectResource;
+
 import org.joda.time.*;
 import play.data.validation.*;
 import play.db.ebean.*;
@@ -62,4 +65,25 @@
     public Duration ago() {
         return JodaDateUtil.ago(this.date);
     }
+
+    public ProjectResource asResource() {
+
+        return new ProjectResource() {
+            @Override
+            public Long getId() {
+                return id;
+            }
+
+            @Override
+            public Project getProject() {
+                return issue.project;
+            }
+
+            @Override
+            public Resource getType() {
+                return Resource.ISSUE_COMMENT;
+            }
+        };
+    }
 }
+
app/models/IssueLabel.java
--- app/models/IssueLabel.java
+++ app/models/IssueLabel.java
@@ -14,6 +14,10 @@
 import play.db.ebean.*;
 
 import javax.persistence.*;
+
+import models.enumeration.Resource;
+import models.resource.ProjectResource;
+
 import java.util.*;
 
 @Entity
@@ -64,4 +68,23 @@
         }
         super.delete();
     }
+
+    public ProjectResource asResource() {
+        return new ProjectResource() {
+            @Override
+            public Long getId() {
+                return id;
+            }
+
+            @Override
+            public Project getProject() {
+                return project;
+            }
+
+            @Override
+            public Resource getType() {
+                return Resource.ISSUE_LABEL;
+            }
+        };
+    }
 }
(No newline at end of file)
 
app/models/Permission.java (deleted)
--- app/models/Permission.java
@@ -1,70 +0,0 @@
-package models;
-
-import java.util.List;
-
-import javax.persistence.Entity;
-import javax.persistence.Id;
-import javax.persistence.ManyToMany;
-
-import models.enumeration.Operation;
-import models.enumeration.Resource;
-import models.enumeration.RoleType;
-import play.db.ebean.Model;
-
-/**
- * @author "Hwi Ahn"
- */
-@Entity
-public class Permission extends Model {
-    private static final long serialVersionUID = 1L;
-    private static Finder<Long, Permission> find = new Finder<Long, Permission>(
-            Long.class, Permission.class);
-
-    @Id
-    public Long id;
-
-    public String resource;
-    public String operation;
-
-    @ManyToMany(targetEntity = models.Role.class, mappedBy = "permissions")
-    public List<Role> roles;
-
-    /**
-     * 해당 유저가 해당 프로젝트에서 해당 리소스와 오퍼레이션을 위한 퍼미션을 가지고 있는지 확인합니다.
-     * 
-     * @param userId
-     * @param projectId
-     * @param resource
-     * @param operation
-     * @return
-     */
-    public static boolean hasPermission(Long userId, Long projectId,
-            Resource resource, Operation operation) {
-        int findRowCount = find.where()
-                                    .eq("roles.projectUsers.user.id", userId)
-                                    .eq("roles.projectUsers.project.id", projectId)
-                                    .eq("resource", resource.resource())
-                                    .eq("operation", operation.operation())
-                                .findRowCount();
-        return (findRowCount != 0) ? true : false;
-    }
-    
-    public static boolean hasPermission(RoleType roleType, Resource resource, Operation operation) {
-        int findRowCount = find.where()
-                                .eq("roles.id", roleType.roleType())
-                                .eq("resource", resource.resource())
-                                .eq("operation", operation.operation())
-                            .findRowCount();
-        return (findRowCount != 0) ? true : false;
-    }
-
-    /**
-     * 해당 롤이 가지고 있는 퍼미션들의 리스트를 반환합니다.
-     * 
-     * @param roleId
-     * @return
-     */
-    public static List<Permission> findPermissionsByRole(Long roleId) {
-        return find.where().eq("roles.id", roleId).findList();
-    }
-}
app/models/Post.java
--- app/models/Post.java
+++ app/models/Post.java
@@ -7,6 +7,7 @@
 import com.avaje.ebean.*;
 import controllers.*;
 import models.enumeration.*;
+import models.resource.ProjectResource;
 import models.support.*;
 import org.joda.time.*;
 import play.data.format.*;
@@ -32,7 +33,7 @@
 
     @Constraints.Required
     public String contents;
- 
+
     @Constraints.Required
     @Formats.DateTime(pattern = "YYYY/MM/DD/hh/mm/ss")
     public Date date;
@@ -114,7 +115,7 @@
         }
         post.update();
     }
-    
+
     public static boolean isAuthor(Long currentUserId, Long id) {
         int findRowCount = finder.where().eq("authorId", currentUserId).eq("id", id).findRowCount();
         return (findRowCount != 0) ? true : false;
@@ -140,4 +141,23 @@
         post.commentCount--;
         post.update();
     }
+
+    public ProjectResource asResource() {
+        return new ProjectResource() {
+            @Override
+            public Long getId() {
+                return id;
+            }
+
+            @Override
+            public Project getProject() {
+                return project;
+            }
+
+            @Override
+            public Resource getType() {
+                return Resource.BOARD_POST;
+            }
+        };
+    }
 }
app/models/Project.java
--- app/models/Project.java
+++ app/models/Project.java
@@ -12,14 +12,15 @@
 import javax.persistence.OneToOne;
 import javax.servlet.ServletException;
 
+import models.enumeration.Resource;
 import models.enumeration.RoleType;
+import models.resource.GlobalResource;
 import models.task.TaskBoard;
 
 import org.eclipse.jgit.api.errors.GitAPIException;
 import org.eclipse.jgit.api.errors.NoHeadException;
 import org.joda.time.Duration;
 
-import play.Logger;
 import play.data.validation.Constraints;
 import play.db.ebean.Model;
 import playRepository.Commit;
@@ -30,7 +31,7 @@
 import com.avaje.ebean.Page;
 
 /**
- * 
+ *
  * @author "Hwi Ahn"
  */
 
@@ -99,7 +100,7 @@
 	/**
 	 * 해당 프로젝트가 존재하는지 여부를 검사합니다. 해당 파라미터에 대하여 프로젝트가 존재하면 true, 존재하지 않으면 false를
 	 * 반환합니다.
-	 * 
+	 *
 	 * @param userName
 	 * @param projectName
 	 * @return
@@ -112,7 +113,7 @@
 
 	/**
 	 * 프로젝트 이름을 해당 이름(projectName)으로 변경이 가능한지 검사합니다.
-	 * 
+	 *
 	 * @param id
 	 * @param userName
 	 * @param projectName
@@ -128,7 +129,7 @@
 	/**
 	 * 해당 유저가 속해있는 프로젝트들 중에서 해당 유저가 유일한 Manager인 프로젝트가 있는지 검사하고, 있다면 그 프로젝트들의
 	 * 리스트를 반환합니다.
-	 * 
+	 *
 	 * @param userId
 	 * @return
 	 */
@@ -150,7 +151,7 @@
 
 	/**
 	 * 해당 유저가 속해있는 프로젝트들의 리스트를 제공합니다.
-	 * 
+	 *
 	 * @param ownerId
 	 * @return
 	 */
@@ -209,4 +210,21 @@
 			return null;
 		}
 	}
-}
+
+	public GlobalResource asResource() {
+	    return new GlobalResource() {
+
+            @Override
+            public Long getId() {
+                return id;
+            }
+
+            @Override
+            public Resource getType() {
+                return Resource.PROJECT;
+            }
+
+	    };
+	}
+
+}
(No newline at end of file)
app/models/Role.java
--- app/models/Role.java
+++ app/models/Role.java
@@ -20,16 +20,11 @@
     private static Finder<Long, Role> find = new Finder<Long, Role>(Long.class,
             Role.class);
 
-
-
     @Id
     public Long id;
 
     public String name;
     public boolean active;
-
-    @ManyToMany
-    public List<Permission> permissions;
 
     @OneToMany(mappedBy = "role", cascade = CascadeType.ALL)
     public List<ProjectUser> projectUsers;
 
app/models/SiteAdmin.java (added)
+++ app/models/SiteAdmin.java
@@ -0,0 +1,28 @@
+package models;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.OneToOne;
+
+import play.db.ebean.Model;
+
+@Entity
+public class SiteAdmin extends Model {
+    /**
+     *
+     */
+    private static final long serialVersionUID = 1L;
+
+    @Id
+    public Long id;
+
+    @OneToOne
+    public User admin;
+
+    public static Model.Finder<Long, SiteAdmin> find = new Finder<Long, SiteAdmin>(Long.class,
+            SiteAdmin.class);
+
+    public static boolean exists(User user) {
+        return user != null && find.where().eq("admin.id", user.id).findRowCount() > 0;
+    }
+}(No newline at end of file)
app/models/User.java
--- app/models/User.java
+++ app/models/User.java
@@ -16,14 +16,13 @@
 import models.enumeration.Matching;
 import models.enumeration.Resource;
 import models.enumeration.RoleType;
+import models.resource.GlobalResource;
 import models.support.FinderTemplate;
 import models.support.OrderParams;
 import models.support.SearchParams;
-import play.cache.Cached;
 import play.data.format.Formats;
 import play.data.validation.Constraints.*;
 import play.db.ebean.Model;
-import play.db.ebean.Model.Finder;
 import utils.JodaDateUtil;
 
 import com.avaje.ebean.Page;
@@ -49,13 +48,13 @@
     public String password;
     public String passwordSalt;
 
-    @Email(message = "user.wrongEmail.alert") 
+    @Email(message = "user.wrongEmail.alert")
     public String email;
 
     public String avatarUrl;
-    
+
     public boolean rememberMe;
-    
+
     @Formats.DateTime(pattern = "yyyy-MM-dd")
     public Date date;
 
@@ -71,7 +70,7 @@
         SimpleDateFormat sdf = new SimpleDateFormat("MMM dd yyyy");
         return sdf.format(this.date);
     }
-    
+
     public List<Project> myProjects(){
         return Project.findProjectsByMember(id);
     }
@@ -97,7 +96,7 @@
 
     /**
      * 존재하는 유저인지를 검사합니다.
-     * 
+     *
      * @param loginId
      * @return
      */
@@ -116,7 +115,7 @@
 
     /**
      * Site manager를 제외한 사이트에 가입된 유저들의 리스트를 Page 형태로 반환합니다.
-     * 
+     *
      * @param pageNum
      * @param loginId
      * @return
@@ -139,7 +138,7 @@
 
     /**
      * 해당 프로젝트에 속하는 유저들의 리스트를 제공합니다. (Site manager는 hidden role로서 반환되지 않습니다.)
-     * 
+     *
      * @param projectId
      * @return
      */
@@ -148,7 +147,7 @@
                 .ne("projectUser.role.id", RoleType.SITEMANAGER.roleType())
                 .findList();
     }
-        
+
     public Long avatarId(){
         return Attachment.findByContainer(Resource.USER_AVATAR, id).get(0).id;
     }
@@ -157,4 +156,26 @@
         User user = find.where().ieq("email", emailAddress).findUnique();
         return user != null;
     }
+
+    public boolean isAnonymous() {
+        return this == UserApp.anonymous;
+    }
+
+    public GlobalResource asResource() {
+        return new GlobalResource() {
+            @Override
+            public Long getId() {
+                return null;
+            }
+
+            @Override
+            public Resource getType() {
+                return Resource.USER;
+            }
+        };
+    }
+
+    public boolean isSiteManager() {
+        return SiteAdmin.exists(this);
+    }
 }
app/models/enumeration/Operation.java
--- app/models/enumeration/Operation.java
+++ app/models/enumeration/Operation.java
@@ -1,18 +1,18 @@
 package models.enumeration;
 
 public enum Operation {
-    READ("read"), WRITE("write"), EDIT("edit"), DELETE("delete");
-    
+    READ("read"), UPDATE("edit"), DELETE("delete");
+
     private String operation;
-    
+
     Operation(String operation) {
         this.operation = operation;
     }
-    
+
     public String operation() {
         return this.operation;
     }
-    
+
     public static Operation getValue(String value) {
         for (Operation operation : Operation.values()) {
             if (operation.operation().equals(value)) {
@@ -21,4 +21,4 @@
         }
         return Operation.READ;
     }
-}
+}
(No newline at end of file)
app/models/enumeration/Resource.java
--- app/models/enumeration/Resource.java
+++ app/models/enumeration/Resource.java
@@ -40,4 +40,4 @@
         }
         return Resource.ISSUE_POST;
     }
-}
+}
(No newline at end of file)
 
app/models/resource/GlobalResource.java (added)
+++ app/models/resource/GlobalResource.java
@@ -0,0 +1,8 @@
+package models.resource;
+
+import models.enumeration.Resource;
+
+public interface GlobalResource {
+    public Long getId();
+    public Resource getType();
+}
 
app/models/resource/ProjectResource.java (added)
+++ app/models/resource/ProjectResource.java
@@ -0,0 +1,10 @@
+package models.resource;
+
+import models.Project;
+import models.enumeration.Resource;
+
+public interface ProjectResource {
+    public Long getId();
+    public Project getProject();
+    public Resource getType();
+}(No newline at end of file)
app/playRepository/GitRepository.java
--- app/playRepository/GitRepository.java
+++ app/playRepository/GitRepository.java
@@ -6,6 +6,10 @@
 import java.util.LinkedList;
 import java.util.List;
 
+import models.Project;
+import models.enumeration.Resource;
+import models.resource.ProjectResource;
+
 import org.codehaus.jackson.node.ObjectNode;
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.api.LogCommand;
@@ -18,6 +22,8 @@
 import org.eclipse.jgit.treewalk.filter.PathFilter;
 import org.eclipse.jgit.treewalk.EmptyTreeIterator;
 import org.eclipse.jgit.diff.DiffEntry;
+
+import controllers.ProjectApp;
 
 import play.Logger;
 import play.libs.Json;
@@ -35,8 +41,12 @@
     }
 
     private final Repository repository;
+    private final String ownerName;
+    private final String projectName;
 
     public GitRepository(String userName, String projectName) throws IOException {
+        this.ownerName = userName;
+        this.projectName = projectName;
         this.repository = createGitRepository(userName, projectName);
     }
 
@@ -108,7 +118,7 @@
 
             RevCommit commit = git.log().addPath(path).call().iterator().next();
             ObjectNode result = Json.newObject();
-            
+
             result.put("type", "file");
             result.put("msg", commit.getShortMessage());
             result.put("author", commit.getAuthorIdent().getName());
@@ -168,7 +178,7 @@
 
             RevCommit commit = git.log().addPath(path).call().iterator().next();
             ObjectNode result = Json.newObject();
-            
+
             result.put("type", "file");
             result.put("msg", commit.getShortMessage());
             result.put("author", commit.getAuthorIdent().getName());
@@ -190,7 +200,7 @@
             NoHeadException {
         ObjectNode result = Json.newObject();
         result.put("type", "folder");
-        
+
         ObjectNode listData = Json.newObject();
 
         while (treeWalk.next()) {
@@ -284,5 +294,24 @@
         return new ArrayList<String>(repository.getAllRefs().keySet());
     }
 
+    @Override
+    public ProjectResource asResource() {
+        return new ProjectResource() {
+            @Override
+            public Long getId() {
+                return null;
+            }
 
+            @Override
+            public Project getProject() {
+                return ProjectApp.getProject(ownerName, projectName);
+            }
+
+            @Override
+            public Resource getType() {
+                return Resource.CODE;
+            }
+
+        };
+    }
 }
app/playRepository/PlayRepository.java
--- app/playRepository/PlayRepository.java
+++ app/playRepository/PlayRepository.java
@@ -3,6 +3,8 @@
 import java.io.IOException;
 import java.util.List;
 
+import models.resource.ProjectResource;
+
 import org.codehaus.jackson.node.ObjectNode;
 import org.eclipse.jgit.api.errors.*;
 import org.eclipse.jgit.errors.*;
@@ -11,26 +13,26 @@
 
 public interface PlayRepository {
 
-    public void create() throws IOException, ClientException;
+    public abstract void create() throws IOException, ClientException;
 
-
-    public ObjectNode findFileInfo(String path) throws IOException, NoHeadException,
+    public abstract ObjectNode findFileInfo(String path) throws IOException, NoHeadException,
             GitAPIException, SVNException;
 
-    public byte[] getRawFile(String path) throws MissingObjectException,
+    public abstract byte[] getRawFile(String path) throws MissingObjectException,
             IncorrectObjectTypeException, AmbiguousObjectException, IOException, SVNException;
 
-    public void delete();
+    public abstract void delete();
 
-    public String getPatch(String commitId) throws GitAPIException, MissingObjectException,
+    public abstract String getPatch(String commitId) throws GitAPIException, MissingObjectException,
             IncorrectObjectTypeException, IOException, SVNException;
 
-    public List<Commit> getHistory(int page, int limit, String branch) throws AmbiguousObjectException,
+    public abstract List<Commit> getHistory(int page, int limit, String branch) throws AmbiguousObjectException,
             IOException, NoHeadException, GitAPIException, SVNException;
 
-    public List<String> getBranches();
+    public abstract List<String> getBranches();
 
 
-    public ObjectNode findFileInfo(String branch, String path) throws AmbiguousObjectException, IOException, SVNException, NoHeadException, GitAPIException;
+    public abstract ObjectNode findFileInfo(String branch, String path) throws AmbiguousObjectException, IOException, SVNException, NoHeadException, GitAPIException;
 
+    public abstract ProjectResource asResource();
 }
(No newline at end of file)
app/playRepository/SVNRepository.java
--- app/playRepository/SVNRepository.java
+++ app/playRepository/SVNRepository.java
@@ -5,6 +5,10 @@
 
 import javax.servlet.*;
 
+import models.Project;
+import models.enumeration.Resource;
+import models.resource.ProjectResource;
+
 import org.codehaus.jackson.node.ObjectNode;
 
 import org.eclipse.jgit.api.errors.GitAPIException;
@@ -17,6 +21,8 @@
 import org.tmatesoft.svn.core.wc.SVNClientManager;
 import org.tmatesoft.svn.core.wc.SVNDiffClient;
 import org.tmatesoft.svn.core.wc.SVNRevision;
+
+import controllers.ProjectApp;
 
 import play.libs.Json;
 
@@ -35,68 +41,69 @@
         SVNRepository.repoPrefix = repoPrefix;
     }
 
-    private String projectName;
+    private final String projectName;
 
-    private String userName;
+    private final String ownerName;
 
     public SVNRepository(final String userName, String projectName) throws ServletException {
-        this.userName = userName;
+        this.ownerName = userName;
         this.projectName = projectName;
     }
 
-
+    @Override
     public byte[] getRawFile(String path) throws SVNException {
-        SVNURL svnURL = SVNURL.fromFile(new File(getRepoPrefix() + userName + "/" + projectName));
+        SVNURL svnURL = SVNURL.fromFile(new File(getRepoPrefix() + ownerName + "/" + projectName));
         org.tmatesoft.svn.core.io.SVNRepository repository = SVNRepositoryFactory.create(svnURL);
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
         repository.getFile(path, -1l, null, baos);
         return baos.toByteArray();
     }
 
+    @Override
     public ObjectNode findFileInfo(String path) throws SVNException {
-        SVNURL svnURL = SVNURL.fromFile(new File(getRepoPrefix() + userName + "/" + projectName));
+        SVNURL svnURL = SVNURL.fromFile(new File(getRepoPrefix() + ownerName + "/" + projectName));
         org.tmatesoft.svn.core.io.SVNRepository repository = SVNRepositoryFactory.create(svnURL);
-        
+
         SVNNodeKind nodeKind = repository.checkPath(path , -1 );
-        
+
         if(nodeKind == SVNNodeKind.DIR){
             //폴더 내용 출력
             ObjectNode result = Json.newObject();
-            
+
             result.put("type", "folder");
             ObjectNode listData = Json.newObject();
-            
+
             SVNProperties prop = new SVNProperties();
-            
+
             Collection entries = repository.getDir(path, -1, prop, (Collection)null);
-            
+
             Iterator iterator = entries.iterator( );
             while ( iterator.hasNext( ) ) {
                 SVNDirEntry entry = ( SVNDirEntry ) iterator.next( );
-                
+
                 ObjectNode data = Json.newObject();
                 data.put("type", entry.getKind() == SVNNodeKind.DIR ? "folder" : "file");
                 data.put("msg", entry.getCommitMessage());
                 data.put("author", entry.getAuthor());
                 data.put("date", entry.getDate().toString());
-                
+
                 listData.put(entry.getName(), data);
             }
             result.put("data", listData);
-            
+
             return result;
-            
+
         } else if(nodeKind == SVNNodeKind.FILE) {
             //파일 내용 출력
             ObjectNode result = Json.newObject();
-            
-            
+
+
             ByteArrayOutputStream baos = new ByteArrayOutputStream();
             SVNProperties prop = new SVNProperties();
             repository.getFile(path, -1l, prop, baos);
-            
+
             result.put("type", "file");
-            
+
             result.put("msg", prop.getStringValue(SVNProperty.COMMITTED_REVISION));
             result.put("author", prop.getStringValue(SVNProperty.LAST_AUTHOR));
 
@@ -111,49 +118,49 @@
     @Override
     public ObjectNode findFileInfo(String branch, String path) throws AmbiguousObjectException,
             IOException, SVNException {
-        SVNURL svnURL = SVNURL.fromFile(new File(getRepoPrefix() + userName + "/" + projectName));
+        SVNURL svnURL = SVNURL.fromFile(new File(getRepoPrefix() + ownerName + "/" + projectName));
         org.tmatesoft.svn.core.io.SVNRepository repository = SVNRepositoryFactory.create(svnURL);
-        
+
         SVNNodeKind nodeKind = repository.checkPath(path , -1 );
-        
+
         if(nodeKind == SVNNodeKind.DIR){
             //폴더 내용 출력
             ObjectNode result = Json.newObject();
-            
+
             result.put("type", "folder");
             ObjectNode listData = Json.newObject();
-            
+
             SVNProperties prop = new SVNProperties();
-            
+
             Collection entries = repository.getDir(path, -1, prop, (Collection)null);
-            
+
             Iterator iterator = entries.iterator( );
             while ( iterator.hasNext( ) ) {
                 SVNDirEntry entry = ( SVNDirEntry ) iterator.next( );
-                
+
                 ObjectNode data = Json.newObject();
                 data.put("type", entry.getKind() == SVNNodeKind.DIR ? "folder" : "file");
                 data.put("msg", entry.getCommitMessage());
                 data.put("author", entry.getAuthor());
                 data.put("date", entry.getDate().toString());
-                
+
                 listData.put(entry.getName(), data);
             }
             result.put("data", listData);
-            
+
             return result;
-            
+
         } else if(nodeKind == SVNNodeKind.FILE) {
             //파일 내용 출력
             ObjectNode result = Json.newObject();
-            
-            
+
+
             ByteArrayOutputStream baos = new ByteArrayOutputStream();
             SVNProperties prop = new SVNProperties();
             repository.getFile(path, -1l, prop, baos);
-            
+
             result.put("type", "file");
-            
+
             result.put("msg", prop.getStringValue(SVNProperty.COMMITTED_REVISION));
             result.put("author", prop.getStringValue(SVNProperty.LAST_AUTHOR));
 
@@ -168,20 +175,20 @@
 
     @Override
     public void create() throws ClientException {
-        String svnPath = new File(SVNRepository.getRepoPrefix() + userName + "/" + projectName)
+        String svnPath = new File(SVNRepository.getRepoPrefix() + ownerName + "/" + projectName)
                 .getAbsolutePath();
         new org.tigris.subversion.javahl.SVNAdmin().create(svnPath, false, false, null, "fsfs");
     }
 
     @Override
     public void delete() {
-        FileUtil.rm_rf(new File(getRepoPrefix() + userName + "/" + projectName));
+        FileUtil.rm_rf(new File(getRepoPrefix() + ownerName + "/" + projectName));
     }
 
     @Override
     public String getPatch(String commitId) throws SVNException {
         // Prepare required arguments.
-        SVNURL svnURL = SVNURL.fromFile(new File(getRepoPrefix() + userName + "/" + projectName));
+        SVNURL svnURL = SVNURL.fromFile(new File(getRepoPrefix() + ownerName + "/" + projectName));
         long rev = Integer.parseInt(commitId);
 
         // Get diffClient.
@@ -201,7 +208,7 @@
     public List<Commit> getHistory(int page, int limit, String until) throws AmbiguousObjectException,
             IOException, NoHeadException, GitAPIException, SVNException {
         // Get the repository
-        SVNURL svnURL = SVNURL.fromFile(new File(repoPrefix + userName + "/" + projectName));
+        SVNURL svnURL = SVNURL.fromFile(new File(repoPrefix + ownerName + "/" + projectName));
         org.tmatesoft.svn.core.io.SVNRepository repository = SVNRepositoryFactory.create(svnURL);
 
         // path to get log
@@ -228,5 +235,24 @@
         return new ArrayList<String>();
     }
 
-    
+    @Override
+    public ProjectResource asResource() {
+        return new ProjectResource() {
+            @Override
+            public Long getId() {
+                return null;
+            }
+
+            @Override
+            public Project getProject() {
+                return ProjectApp.getProject(ownerName, projectName);
+            }
+
+            @Override
+            public Resource getType() {
+                return Resource.CODE;
+            }
+
+        };
+    }
 }
app/utils/AccessControl.java
--- app/utils/AccessControl.java
+++ app/utils/AccessControl.java
@@ -3,101 +3,146 @@
 import models.Comment;
 import models.Issue;
 import models.IssueComment;
-import models.Permission;
 import models.Post;
 import models.Project;
 import models.ProjectUser;
+import models.User;
 import models.enumeration.Operation;
 import models.enumeration.Resource;
-import models.enumeration.RoleType;
-
-import org.h2.util.StringUtils;
+import models.resource.GlobalResource;
+import models.resource.ProjectResource;
 
 import play.db.ebean.Model;
 import play.db.ebean.Model.Finder;
 
 /**
  * @author "Hwi Ahn"
+ * @author "Yi EungJun"
  */
 public class AccessControl {
 
-	/**
-	 * 
-	 * @param userId
-	 * @param projectId
-	 * @param resource
-	 * @param operation
-	 * @param resourceId
-	 * @return
-	 */
-	public static boolean isAllowed(Object userSessionId, Long projectId,
-			Resource resource, Operation operation, Long resourceId) {
-		Long userId = 0l;
-		if (userSessionId instanceof String) {
-			if (StringUtils.isNumber(userSessionId.toString())) {
-				userId = Long.parseLong((String) userSessionId);
-			}
-		} else {
-			userId = (Long) userSessionId;
-		}
+    public static boolean isCreatable(User user, Resource resourceType) {
+        // Only login users can create a resource.
+        return !user.isAnonymous();
+    }
 
-		// Site administrator is all-mighty.
-		/*
-		 * if (Permission.hasPermission(userId, 0l, Resource.SITE_SETTING,
-		 * Operation.WRITE)) { return true; }
-		 */
+    public static boolean isCreatable(User user, Project project, Resource resourceType) {
+        if (user.isSiteManager()) {
+            return true;
+        }
 
-		boolean isAuthorEditible;
+        if (ProjectUser.isMember(user.id, project.id)) {
+            // Project members can create anything.
+            return true;
+        } else {
+            // If the project is private, nonmembers cannot create anything.
+            if (!project.share_option) {
+                return false;
+            }
 
-		switch (resource) {
-		case ISSUE_POST:
-			isAuthorEditible = isAuthor(userId, resourceId,
-					new Finder<Long, Issue>(Long.class, Issue.class))
-					&& Project.find.byId(projectId).isAuthorEditable;
-			break;
-		case ISSUE_COMMENT:
-			isAuthorEditible = isAuthor(userId, resourceId,
-					new Finder<Long, IssueComment>(Long.class,
-							IssueComment.class));
-			break;
-		case BOARD_POST:
-			isAuthorEditible = isAuthor(userId, resourceId,
-					new Finder<Long, Post>(Long.class, Post.class));
-			break;
-		case BOARD_COMMENT:
-			isAuthorEditible = isAuthor(userId, resourceId,
-					new Finder<Long, Comment>(Long.class, Comment.class));
-			break;
-		case USER:
-			return userId == resourceId;
-		case ISSUE_LABEL:
-			return ProjectUser.isMember(userId, projectId);
-		case PROJECT:
-			return Project.find.byId(projectId).share_option
-					|| ProjectUser.isMember(userId, projectId);
-		case USER_AVATAR:	// Public Acess
-			return true;
-		default:
-			isAuthorEditible = false;
-			break;
-		}
-		
-		if (ProjectUser.isMember(userId, projectId)) {
-			return isAuthorEditible
-					|| Permission.hasPermission(userId, projectId, resource,
-							operation);
-		} else { // Anonymous
-			if (!Project.find.byId(projectId).share_option) {
-				return false;
-			}
-			return isAuthorEditible
-					|| Permission.hasPermission(RoleType.ANONYMOUS, resource,
-							operation);
-		}
-	}
+            // If the project is public, login users can create issues and posts.
+            if (project.share_option && !user.isAnonymous()) {
+                switch(resourceType){
+                case ISSUE_POST:
+                case BOARD_POST:
+                    return true;
+                default:
+                    return false;
+                }
+            }
+
+            return false;
+        }
+    }
+
+    public static boolean isAllowed(User user, GlobalResource resource, Operation operation) {
+        if (user.isSiteManager()) {
+            return true;
+        }
+
+        if (operation.equals(Operation.READ)) {
+            if (resource.getType().equals(Resource.PROJECT)) {
+                Project project = Project.find.byId(resource.getId());
+                return project.share_option || ProjectUser.isMember(user.id, project.id);
+            }
+
+            // anyone can read all resources which doesn't belong to a project.
+            return true;
+        }
+
+        // UPDATE, DELETE
+        switch(resource.getType()){
+        case USER:
+        case USER_AVATAR:
+            return user.id == resource.getId();
+        case PROJECT:
+            return ProjectUser.isManager(user.id, resource.getId());
+        default:
+            // undefined
+            return false;
+        }
+    }
+
+    public static boolean isAllowed(User user, ProjectResource resource, Operation operation) {
+        if (user.isSiteManager()) {
+            return true;
+        }
+
+        Project project = resource.getProject();
+
+        if (ProjectUser.isManager(user.id, project.id)) {
+            return true;
+        }
+
+        switch(operation) {
+        case READ:
+            // If the project is public anyone can read its resources else only members can do that.
+            return project.share_option || ProjectUser.isMember(user.id, project.id);
+        case UPDATE:
+            // Any members can update repository
+            if (resource.getType() == Resource.CODE) {
+                return ProjectUser.isMember(user.id, project.id);
+            } else {
+                return isEditableAsAuthor(user, project, resource);
+            }
+        case DELETE:
+            return isEditableAsAuthor(user, project, resource);
+        default:
+            // undefined
+            return false;
+        }
+    }
+
+    private static boolean isEditableAsAuthor(User user, Project project, ProjectResource resource) {
+        // author can update or delete her/his own article.
+        if (!project.isAuthorEditable) {
+            return false;
+        }
+
+        Finder<Long, ?> finder;
+
+        switch (resource.getType()) {
+        case ISSUE_POST:
+            finder = new Finder<Long, Issue>(Long.class, Issue.class);
+            break;
+        case ISSUE_COMMENT:
+            finder = new Finder<Long, IssueComment>(Long.class, IssueComment.class);
+            break;
+        case BOARD_POST:
+            finder = new Finder<Long, Post>(Long.class, Post.class);
+            break;
+        case BOARD_COMMENT:
+            finder = new Finder<Long, Comment>(Long.class, Comment.class);
+            break;
+        default:
+            return false;
+        }
+
+        return isAuthor(user.id, resource.getId(), finder);
+    }
 
 	/**
-	 * 
+	 *
 	 * @param userId
 	 * @param resourceId
 	 * @param finder
@@ -108,14 +153,5 @@
 		int findRowCount = finder.where().eq("authorId", userId)
 				.eq("id", resourceId).findRowCount();
 		return (findRowCount != 0) ? true : false;
-	}
-
-	public static boolean isAllowed(Object userSessionId, Project project) {
-		if (isAllowed(userSessionId, project.id, Resource.PROJECT,
-				Operation.READ, project.id)) {
-			return true;
-		} else {
-			return false;
-		}
 	}
 }
(No newline at end of file)
app/views/board/post.scala.html
--- app/views/board/post.scala.html
+++ app/views/board/post.scala.html
@@ -1,6 +1,7 @@
 @(post:Post, commentForm:Form[Comment],  project:Project)
 
 @import utils.TemplateHelper._
+@import utils.AccessControl._
 @import models.enumeration.Resource
 @implicitField = @{ helper.FieldConstructor(simpleForm) }
 
@@ -42,7 +43,7 @@
         <a href="@routes.UserApp.userInfo(comment.authorLoginId)" class="pull-left img-rounded">
         <img class="user-picture" src="@User.findByLoginId(comment.authorLoginId).avatarUrl" width="34" height="34" alt="@comment.authorName">
         <div class="media-body">
-          @roleCheck(session.get("userId"), project.id, Resource.BOARD_COMMENT, Operation.DELETE, comment.id){
+          @if(isAllowed(UserApp.currentUser(), comment.asResource(), Operation.DELETE)){
           <a class="pull-right close" href="@routes.BoardApp.deleteComment(project.owner, project.name, post.id, comment.id)">&times;</a>
           }
             <p class="commenter"><a href="@routes.UserApp.userInfo(comment.authorLoginId)"><strong>@comment.authorName</strong></a><!-- <span class="name">(Sam sstephenson)</span> --> <span class="date">@utils.TemplateHelper.agoString(post.ago())</span></p>
@@ -56,7 +57,7 @@
       </li>
       }
     </ul>
-      @roleCheck(session.get("userId"), project.id, Resource.BOARD_COMMENT, Operation.WRITE){
+      @if(isCreatable(User.findByLoginId(session.get("loginId")), project, models.enumeration.Resource.BOARD_POST)){
       <div class="write-comment-box">
         @helper.form(routes.BoardApp.newComment(project.owner, project.name, post.id), 'class->"nm", 'enctype -> "multipart/form-data"){
           <div class="write-comment-wrap">
@@ -120,12 +121,12 @@
   <div class="board-footer">
       <!--<a href="/add-notification" class="add-btn"><i class="ico ico-plus-blue"></i>자동알림추가</a>-->
       <a href="@routes.BoardApp.posts(project.owner, project.name)" class="n-btn gray small">LIST</a>
-      @roleCheck(session.get("userId"), project.id, Resource.BOARD_POST, Operation.DELETE){
+      @isAllowed(UserApp.currentUser(), post.asResource(), Operation.DELETE)){
       <a data-toggle="modal" href="#deleteConfirm" class="n-btn gray small">DELETE</a>
-    }
-      @roleCheck(session.get("userId"), project.id, Resource.BOARD_POST, Operation.EDIT){
+      }
+      @isAllowed(UserApp.currentUser(), post.asResource(), Operation.UPDATE)){
       <a href="@routes.BoardApp.editPost(project.owner, project.name, post.id)" class="n-btn blue small">EDIT</a>
-    }
+      }
   </div>
 
   <!--삭제확인상자-->
app/views/board/postList.scala.html
--- app/views/board/postList.scala.html
+++ app/views/board/postList.scala.html
@@ -1,6 +1,7 @@
 @(title:String, project:Project, page:com.avaje.ebean.Page[Post], param:BoardApp.SearchCondition)
 
 @import utils.TemplateHelper._
+@import utils.AccessControl._
 
 @header(label:String, key:String) = {
   <th>
@@ -72,7 +73,7 @@
     }
   </ul>
   }
-  @roleCheck(session.get("userId"), project.id, models.enumeration.Resource.BOARD_POST, models.enumeration.Operation.WRITE){
+  @if(isCreatable(User.findByLoginId(session.get("loginId")), project, models.enumeration.Resource.BOARD_POST)){
   <div class="write-btn-wrap">
     <a href="@routes.BoardApp.newPostForm(project.owner, project.name)" class="n-btn blue small">WRITE</a>
   </div>
app/views/issue/editIssue.scala.html
--- app/views/issue/editIssue.scala.html
+++ app/views/issue/editIssue.scala.html
@@ -3,12 +3,8 @@
 @import scala.collection.mutable.Map
 @implicitFieldConstructor = @{ FieldConstructor(twitterBootstrapInput.render) }
 @import models.enumeration.Resource
-
-@isVisible(resource: models.enumeration.Resource)(content: => Html) = @{
-  roleCheck(session.get("userId"), project.id, resource, models.enumeration.Operation.EDIT){
-    content
-  }
-}
+@import models.enumeration.Operation
+@import utils.AccessControl._
 
 @main(Messages(title), project, utils.MenuType.ISSUE) {
 <div class="page">
@@ -46,14 +42,14 @@
 
     <!-- issue.label js module appends a label selector here. -->
     <fieldset class="labels">
-      @isVisible(models.enumeration.Resource.ISSUE_STATE) {
+      @if(isAllowed(UserApp.currentUser(), issue.stateAsResource(), Operation.UPDATE)){
             @select(
                 issueForm("state"),
                 options(State.OPEN.name -> "Open", State.CLOSED.name -> "Closed"),
                 '_label-> Messages("issue.state"),
                 '_showConstraints -> false)
             }
-      @isVisible(models.enumeration.Resource.ISSUE_ASSIGNEE) {
+      @if(isAllowed(UserApp.currentUser(), issue.assigneeAsResource(), Operation.UPDATE)){
             @select(
                 issueForm("assignee.user.id"),
                 options(ProjectUser.options(project.id)),
@@ -61,7 +57,7 @@
                 '_default -> Messages("issue.new.selectDefault.assignee"),
                 '_showConstraints -> false)
             }
-      @isVisible(models.enumeration.Resource.ISSUE_MILESTONE) {
+      @if(isAllowed(UserApp.currentUser(), issue.milestoneAsResource(), Operation.UPDATE)){
             @select(
                 issueForm("milestoneId"),
                 options(Milestone.options(project.id)),
app/views/issue/issue.scala.html
--- app/views/issue/issue.scala.html
+++ app/views/issue/issue.scala.html
@@ -2,18 +2,15 @@
 @import helper._
 @import scala.collection.mutable.Map
 @import models.enumeration.Resource
+@import models.enumeration.Operation
 @import models.Milestone
 @import java.text.SimpleDateFormat
 @import java.security.MessageDigest
 @import utils.TemplateHelper._
+@import utils.AccessControl._
 
 @implicitFieldConstructor = @{ FieldConstructor(twitterBootstrapInput.render) }
 
-@isVisible(resource: models.enumeration.Resource)(content: => Html) = @{
-  roleCheck(session.get("userId"), project.id, resource, models.enumeration.Operation.EDIT, issue.id){
-    content
-  }
-}
 @main(Messages(title),project, utils.MenuType.ISSUE) {
 <div class="page">
 <style>
@@ -87,7 +84,7 @@
         <span class="author">@comment.authorName</span>
         <span class="date">@comment.date</span>
       </div>
-      @roleCheck(session.get("userId"), project.id, models.enumeration.Resource.ISSUE_COMMENT, models.enumeration.Operation.EDIT, comment.id) {
+      @if(isAllowed(UserApp.currentUser(), comment.asResource(), Operation.DELETE)) {
       <div class="pull-right">
         <a data-toggle="modal" href='#deleteCommentConfirm'><i class="icon-remove"> </i>
       </div>
@@ -127,7 +124,7 @@
 }
 </div>
 
-@isVisible(models.enumeration.Resource.ISSUE_POST) {
+@if(isAllowed(UserApp.currentUser(), issue.asResource(), Operation.DELETE)) {
 <div class="pull-left">
   <a data-toggle="modal" href="#deleteIssueConfirm" class="btn">@Messages("button.delete")</a>
 </div>
@@ -136,7 +133,7 @@
 <div class="pull-right">
     <a class="btn" href=""><i class="icon-ok"></i>@Messages("button.autoNotification")</a>
     <a class="btn" href="@routes.IssueApp.issues(project.owner, project.name,"open")">@Messages("button.list")</a>
-    @isVisible(models.enumeration.Resource.ISSUE_POST) {
+    @if(isAllowed(UserApp.currentUser(), issue.asResource(), Operation.UPDATE)) {
     <a class="btn btn-primary" href="@routes.IssueApp.editIssueForm(project.owner, project.name, issue.id)">@Messages("button.edit")</a>
     }
 </div>
app/views/issue/newIssue.scala.html
--- app/views/issue/newIssue.scala.html
+++ app/views/issue/newIssue.scala.html
@@ -4,12 +4,9 @@
 @import scala.collection.mutable.Map
 @implicitFieldConstructor = @{ FieldConstructor(twitterBootstrapInput.render) }
 @import controllers.UserApp;
-
-@isVisible(resource: models.enumeration.Resource)(content: => Html) = @{
-  roleCheck(session.get("userId"), project.id, resource, models.enumeration.Operation.EDIT){
-    content
-  }
-}
+@import models.enumeration.Resource
+@import models.enumeration.Operation
+@import utils.AccessControl._
 
 @main(Messages(title), project, utils.MenuType.ISSUE) {
 <div class="page">
@@ -49,7 +46,7 @@
 
         <!-- issue.label js module appends a label selector here. -->
         <fieldset class="labels">
-          @isVisible(models.enumeration.Resource.ISSUE_ASSIGNEE) {
+          @if(isCreatable(UserApp.currentUser(), project, Resource.ISSUE_ASSIGNEE)) {
                 @select(
                     issueForm("assignee.user.id"),
                     options(ProjectUser.options(project.id)),
@@ -57,7 +54,7 @@
                     '_default -> Messages("issue.new.selectDefault.assignee"),
                     '_showConstraints -> false)
                 }
-          @isVisible(models.enumeration.Resource.ISSUE_MILESTONE) {
+          @if(isCreatable(UserApp.currentUser(), project, Resource.ISSUE_MILESTONE)) {
                 @select(
                     issueForm("milestoneId"),
                     options(Milestone.options(project.id)),
 
app/views/roleCheck.scala.html (deleted)
--- app/views/roleCheck.scala.html
@@ -1,5 +0,0 @@
-@(userId: String, projectId: Long, resource: models.enumeration.Resource, operation: models.enumeration.Operation, resourceId: Long = null)(content: => Html)
-
-@if(utils.AccessControl.isAllowed(userId, projectId, resource, operation, resourceId)){
-  @content
-}
app/views/topmenu.scala.html
--- app/views/topmenu.scala.html
+++ app/views/topmenu.scala.html
@@ -1,19 +1,13 @@
 @(project:Project, menuType:utils.MenuType)
 
 @import utils._
-
+@import models.enumeration.Resource;
+@import utils.TemplateHelper._
+@import utils.AccessControl._
 
 @isActiveMenu(m_type:MenuType) = @{
     if(m_type == menuType) {"selected"}
 }
-
-@isVisible(resource: models.enumeration.Resource)(content: => Html) = @{
-  roleCheck(session.get("userId"), project.id, resource, models.enumeration.Operation.READ){
-    content
-  }
-}
-@import utils.TemplateHelper._
-
     <div class="gnb-inner">
         <div class="gnb-knob left"><i class="ico gnb-left"></i></div>
         <div class="gnb-menu-wrap">
@@ -22,29 +16,21 @@
                 <li class="menu">
                     <a href="@routes.ProjectApp.project(project.owner, project.name)" class="@isActiveMenu(MenuType.PROJECT_HOME)">PROJECT HOME</a>
                 </li>
-                @isVisible(models.enumeration.Resource.BOARD_POST){
                 <li class="menu">
                     <a href="@routes.BoardApp.posts(project.owner, project.name)" class="@isActiveMenu(MenuType.BOARD)">BOARD</a>
                 </li>
-                }
-                @isVisible(models.enumeration.Resource.CODE){
                 <li class="menu">
                     <a href="@routes.CodeApp.codeBrowser(project.owner, project.name)" class="@isActiveMenu(MenuType.CODE)">CODE</a>
                 </li>
-                }
-                @isVisible(models.enumeration.Resource.ISSUE_POST){
                 <li class="menu">
                     <a href="@routes.IssueApp.issues(project.owner, project.name,"open")" class="@isActiveMenu(MenuType.ISSUE)">ISSUE</a>
                 </li>
-                }
-                @isVisible(models.enumeration.Resource.WIKI_PAGE){
                 <!-- >li class="menu">
                     <a href="@routes.TaskApp.index(project.owner, project.name)">TASK</a>
                 </li-->
-                }
             </ul>
             <ul class="gnb-menus right unstyled">
-                @if(session.contains("userId")){
+                @if(!UserApp.currentUser().isAnonymous()){
                 <li><a href="@routes.UserApp.userInfo(session.get("loginId"))">
                 <img class="user-thumb img-rounded" src="@User.findByLoginId(session.get("loginId")).avatarUrl" alt="avatar" width="22" height="22"></a>
                     <a href="@routes.UserApp.userInfo(session.get("loginId"))">@session.get("userName")</a>
@@ -65,4 +51,3 @@
         </div>
         <div class="gnb-knob right"><i class="ico gnb-right"></i></div>
     </div>
-
conf/initial-data.yml
--- conf/initial-data.yml
+++ conf/initial-data.yml
@@ -40,14 +40,12 @@
         email:          ejlee@nhn.com
         avatarUrl:      /assets/images/default-avatar-128.png
         date:           2012-05-01 08:00:00
-    - !!models.User
-        name:           anonymous
-        loginId:        anonymous
-        password:       
-        email:          anonymous@nhn.com
-        avatarUrl:      /assets/images/default-avatar-128.png
-        date:           2012-02-01 08:00:00
-        
+
+siteAdmins:
+     - !!models.SiteAdmin
+        admin:        !!models.User
+                            id: 1
+
 # Projects
 projects:
     - !!models.Project
@@ -362,576 +360,14 @@
     - !!models.Role
         name:           manager
         active:         true
-        permissions:
-            - !!models.Permission
-                id: 1  
-            - !!models.Permission
-                id: 2 
-            - !!models.Permission
-                id: 3 
-            - !!models.Permission
-                id: 4  
-            - !!models.Permission
-                id: 5 
-            - !!models.Permission
-                id: 6 
-            - !!models.Permission
-                id: 7  
-            - !!models.Permission
-                id: 8 
-            - !!models.Permission
-                id: 12 
-            - !!models.Permission
-                id: 13 
-            - !!models.Permission
-                id: 14  
-            - !!models.Permission
-                id: 15 
-            - !!models.Permission
-                id: 16
-            - !!models.Permission
-                id: 17  
-            - !!models.Permission
-                id: 21  
-            - !!models.Permission
-                id: 22 
-            - !!models.Permission
-                id: 23 
-            - !!models.Permission
-                id: 24  
-            - !!models.Permission
-                id: 25 
-            - !!models.Permission
-                id: 26 
-            - !!models.Permission
-                id: 33 
-            - !!models.Permission
-                id: 34  
-            - !!models.Permission
-                id: 35 
-            - !!models.Permission
-                id: 36 
-            - !!models.Permission
-                id: 37