Beispiel #1
0
  private static Comment makeNewComment(Resource target, User sender, String body)
      throws IssueNotFound, PostingNotFound {
    Comment comment;
    Long id = Long.valueOf(target.getId());

    switch (target.getType()) {
      case ISSUE_POST:
        Issue issue = Issue.finder.byId(id);
        if (issue == null) {
          throw new IssueNotFound(id);
        }
        comment = new IssueComment(issue, sender, body);
        break;
      case BOARD_POST:
        Posting posting = Posting.finder.byId(id);
        if (posting == null) {
          throw new PostingNotFound(id);
        }
        comment = new PostingComment(posting, sender, body);
        break;
      default:
        throw new IllegalArgumentException("Unsupported resource type: " + target.getType());
    }

    return comment;
  }
Beispiel #2
0
  /**
   * 업로드된 {@code file}을 주어진 {@code name}으로 {@code container}에 첨부한다.
   *
   * <p>when: 업로드된 파일이 사용자에게 첨부될 때. 혹은 사용자를 거치지 않고 바로 다른 리소스로 첨부될 때.
   *
   * <p>업로드된 파일을 업로드 디렉토리로 옮긴다. 이 때 파일이름을 그 파일의 해시값으로 변경한다. 그 후 이 파일에 대한 메타정보 및 첨부될 대상에 대한 정보를 이
   * 엔터티에 담는다. 만약 이 엔터티와 같은 내용을 갖고 있는 엔터티가 이미 존재한다면, 이미 {@code container}에 같은 첨부가 존재하고 있으므로 첨부하지 않고
   * {@code false}를 반환한다. 그렇지 않다면 첨부 후 {@code true}를 반환한다.
   *
   * @param file 첨부할 파일
   * @param name 파일 이름
   * @param container 파일이 첨부될 리소스
   * @return 파일이 새로 첨부되었다면 {@code true}, 이미 같은 첨부가 존재하여 첨부되지 않았다면 {@code false}
   * @throws IOException
   * @throws NoSuchAlgorithmException
   */
  @Transient
  public boolean store(File file, String name, Resource container)
      throws IOException, NoSuchAlgorithmException {
    // Store the file as its SHA1 hash in filesystem, and record its
    // metadata - containerType, containerId, size and hash - in Database.
    this.containerType = container.getType();
    this.containerId = container.getId();
    this.createdDate = JodaDateUtil.now();

    if (name == null) {
      this.name = file.getName();
    } else {
      this.name = name;
    }

    if (this.mimeType == null) {
      this.mimeType = FileUtil.detectMediaType(file, name);
    }

    // the size must be set before it is moved.
    this.size = file.length();
    this.hash = Attachment.moveFileIntoUploadDirectory(file);

    // Add the attachment into the Database only if there is no same record.
    Attachment sameAttach = Attachment.findBy(this);
    if (sameAttach == null) {
      super.save();
      return true;
    } else {
      this.id = sameAttach.id;
      return false;
    }
  }
Beispiel #3
0
  /**
   * Create a comment from the given email.
   *
   * @param message
   * @param target
   * @throws MessagingException
   * @throws MailHandlerException
   * @throws IOException
   * @throws NoSuchAlgorithmException
   */
  @Transactional
  public static Comment saveComment(IMAPMessage message, Resource target)
      throws MessagingException, MailHandlerException, IOException, NoSuchAlgorithmException {
    User author = IMAPMessageUtil.extractSender(message);

    if (!AccessControl.isProjectResourceCreatable(author, target.getProject(), target.getType())) {
      throw new PermissionDenied(
          cannotCreateMessage(author, target.getProject(), target.getType()));
    }

    Content parsedMessage = extractContent(message);

    Comment comment = makeNewComment(target, author, parsedMessage.body);

    comment.save();

    Map<String, Attachment> relatedAttachments =
        saveAttachments(parsedMessage.attachments, comment.asResource());

    if (new ContentType(parsedMessage.type).match(MimeType.HTML)) {
      // replace cid with attachments
      comment.contents = replaceCidWithAttachments(comment.contents, relatedAttachments);
      comment.update();
    }

    new OriginalEmail(message.getMessageID(), comment.asResource()).save();

    // Add the event
    addEvent(NotificationEvent.forNewComment(comment, author), message.getAllRecipients(), author);

    return comment;
  }
Beispiel #4
0
  @Transactional
  protected static ReviewComment saveReviewComment(
      Resource target, User sender, Content content, String messageID, Address[] allRecipients)
      throws MessagingException, IOException, NoSuchAlgorithmException {
    ReviewComment comment;
    CommentThread thread = CommentThread.find.byId(Long.valueOf(target.getId()));

    if (thread == null) {
      throw new IllegalArgumentException();
    }

    comment = new ReviewComment();
    comment.setContents(content.body);
    comment.author = new UserIdent(sender);
    comment.thread = thread;
    comment.save();

    Map<String, Attachment> relatedAttachments =
        saveAttachments(content.attachments, comment.asResource());

    if (new ContentType(content.type).match(MimeType.HTML)) {
      // replace cid with attachments
      comment.setContents(replaceCidWithAttachments(comment.getContents(), relatedAttachments));
      comment.update();
    }

    new OriginalEmail(messageID, comment.asResource()).save();

    // Add the event
    if (thread.isOnPullRequest()) {
      addEvent(
          NotificationEvent.forNewComment(sender, thread.pullRequest, comment),
          allRecipients,
          sender);
    } else {
      try {
        String commitId;

        if (thread instanceof CodeCommentThread) {
          commitId = ((CodeCommentThread) thread).commitId;
        } else if (thread instanceof NonRangedCodeCommentThread) {
          commitId = ((NonRangedCodeCommentThread) thread).commitId;
        } else {
          throw new IllegalArgumentException();
        }

        addEvent(
            NotificationEvent.forNewCommitComment(target.getProject(), comment, commitId, sender),
            allRecipients,
            sender);
      } catch (Exception e) {
        Logger.warn("Failed to send a notification", e);
      }
    }

    return comment;
  }
Beispiel #5
0
 /**
  * {@code user}가 {@code project}의 {@code resource}에 대해 저자로서의 수정 권한을 갖는지의 여부를 반환한다.
  *
  * <p>현재는 이슈 및 게시물과 그것들의 댓글에 대해서만 동작한다.
  *
  * @param user
  * @param project
  * @param resource
  * @return {@code user}가 {@code project}의 {@code resource}에 대해 저자로서의 수정 권한을 갖는지의 여부를 반환한다.
  */
 private static boolean isEditableAsAuthor(User user, Project project, Resource resource) {
   switch (resource.getType()) {
     case ISSUE_POST:
     case ISSUE_COMMENT:
     case NONISSUE_COMMENT:
     case BOARD_POST:
     case CODE_COMMENT:
       return resource.getAuthorId().equals(user.id);
     default:
       return false;
   }
 }
Beispiel #6
0
  /**
   * {@code user}가 프로젝트 리소스인 {@code resource}에 {@code operation}을 하는 것이 허용되는지의 여부를 반환한다.
   *
   * <p>See docs/technical/access-control.md for more information.
   *
   * @param user
   * @param project
   * @param resource
   * @param operation
   * @return
   */
  private static boolean isProjectResourceAllowed(
      User user, Project project, Resource resource, Operation operation) {
    if (ProjectUser.isManager(user.id, project.id)) {
      return true;
    }

    // If the resource is an attachment, the permission depends on its container.
    if (resource.getType() == ResourceType.ATTACHMENT) {
      switch (operation) {
        case READ:
          return isAllowed(user, resource.getContainer(), Operation.READ);
        case UPDATE:
        case DELETE:
          return isAllowed(user, resource.getContainer(), Operation.UPDATE);
      }
    }

    // Access Control for members, nonmembers and anonymous.
    // - Anyone can read public project's resource.
    // - Members can update anything and delete anything except code repository.
    // - Nonmember can update or delete a resource if only
    //     * the user is the author of the resource,
    //     * the resource is not a code repository,
    //     * and the project to which the resource belongs is public.
    // See docs/technical/access-control.md for more information.
    switch (operation) {
      case READ:
        return project.isPublic || ProjectUser.isMember(user.id, project.id);
      case UPDATE:
        if (ProjectUser.isMember(user.id, project.id)) {
          return true;
        }

        if (resource.getType() == ResourceType.CODE) {
          // Nonmember cannot update the repository.
          return false;
        } else {
          return project.isPublic && isEditableAsAuthor(user, project, resource);
        }
      case DELETE:
        if (resource.getType() == ResourceType.CODE) {
          return false;
        } else {
          return ProjectUser.isMember(user.id, project.id)
              || (project.isPublic && isEditableAsAuthor(user, project, resource));
        }
      default:
        // undefined
        return false;
    }
  }
Beispiel #7
0
  /**
   * Global 리소스에 대해 주어진 리소스의 operation을 허용하는지 여부
   *
   * <p>임시 업로드 파일은 해당 파일을 업로드한 사용자만 접근할 수 있다. 비공개 프로젝트는 해당 프로젝트의 멤버만 접근할 수 있다. 공개 프로젝트는 모든 사용자가 접근할
   * 수 있다. 사용자 및 사용자의 아바타는 그 사용자 본인만 갱신 혹은 삭제할 수 있다. 프로젝트는 그 프로젝트의 관리자만이 갱신 혹은 삭제할 수 있다.
   *
   * @param user
   * @param resource
   * @param operation
   * @return
   */
  private static boolean isGlobalResourceAllowed(
      User user, Resource resource, Operation operation) {
    // Temporary attachments are allowed only for the user who uploads them.
    if (resource.getType() == ResourceType.ATTACHMENT
        && resource.getContainer().getType() == ResourceType.USER) {
      return user.id.equals(resource.getContainer().getId());
    }

    if (operation == Operation.READ) {
      if (resource.getType() == ResourceType.PROJECT) {
        Project project = Project.find.byId(resource.getId());
        return project != null && (project.isPublic || ProjectUser.isMember(user.id, project.id));
      }

      // anyone can read any resource which is not a project.
      return true;
    }

    // UPDATE, DELETE
    switch (resource.getType()) {
      case USER:
      case USER_AVATAR:
        return user.id.equals(resource.getId());
      case PROJECT:
        return ProjectUser.isManager(user.id, resource.getId());
      default:
        // undefined
        return false;
    }
  }
Beispiel #8
0
 /**
  * {@code from}에 첨부된 파일중 파일 아이디가{@code selectedFileIds}에 해당하는 첨부 파일을 {@code to}로 옮긴다.
  *
  * <p>when: 업로드 직후 일시적으로 사용자에게 첨부되었던 첨부 파일들을, 특정 리소스(이슈, 게시물 등)으로 옮기려 할 때
  *
  * @param from 첨부 파일이 원래 있었던 리소스
  * @param to 첨부 파일이 새로 옮겨질 리소스
  * @return
  */
 public static int moveOnlySelected(Resource from, Resource to, String[] selectedFileIds) {
   if (selectedFileIds.length == 0) {
     return NOTHING_TO_ATTACH;
   }
   List<Attachment> attachments =
       Attachment.find.where().idIn(Arrays.asList(selectedFileIds)).findList();
   for (Attachment attachment : attachments) {
     if (attachment.containerId.equals(from.getId())
         && attachment.containerType == from.getType()) {
       attachment.moveTo(to);
     }
   }
   return attachments.size();
 }
Beispiel #9
0
  /**
   * Create a review comment from the given email.
   *
   * @param message
   * @param target
   * @throws IOException
   * @throws MessagingException
   * @throws PermissionDenied
   * @throws NoSuchAlgorithmException
   */
  static void saveReviewComment(IMAPMessage message, Resource target)
      throws IOException, MessagingException, PermissionDenied, NoSuchAlgorithmException {
    User sender = IMAPMessageUtil.extractSender(message);

    if (!AccessControl.isProjectResourceCreatable(
        sender, target.getProject(), ResourceType.REVIEW_COMMENT)) {
      throw new PermissionDenied(
          cannotCreateMessage(sender, target.getProject(), target.getType()));
    }

    Content content = extractContent(message);
    String messageID = message.getMessageID();
    Address[] allRecipients = message.getAllRecipients();

    saveReviewComment(target, sender, content, messageID, allRecipients);
  }
Beispiel #10
0
  /**
   * {@code user}가 {@code resource}에 {@code operation}을 하는 것이 허용되는지의 여부를 반환한다.
   *
   * @param user
   * @param resource
   * @param operation
   * @return {@code user}가 {@code resource}에 {@code operation}을 하는 것이 허용되는지의 여부
   */
  public static boolean isAllowed(User user, Resource resource, Operation operation) {
    if (user.isSiteManager()) {
      return true;
    }

    Project project = resource.getProject();

    if (project == null) {
      return isGlobalResourceAllowed(user, resource, operation);
    } else {
      return isProjectResourceAllowed(user, project, resource, operation);
    }
  }
Beispiel #11
0
  /**
   * 이 객체를 리소스로 반환한다.
   *
   * <p>when: 권한검사시 사용
   *
   * @return 리소스
   */
  @Override
  public Resource asResource() {
    boolean isContainerProject = containerType.equals(ResourceType.PROJECT);
    final Project project;
    final Resource container;

    if (isContainerProject) {
      project = Project.find.byId(Long.parseLong(containerId));
      if (project == null) {
        throw new RuntimeException(messageForLosingProject());
      }
      container = project.asResource();
    } else {
      container = Resource.get(containerType, containerId);
      if (!(container instanceof GlobalResource)) {
        project = container.getProject();
        if (project == null) {
          throw new RuntimeException(messageForLosingProject());
        }
      } else {
        project = null;
      }
    }

    if (project != null) {
      return new Resource() {
        @Override
        public String getId() {
          return id.toString();
        }

        @Override
        public Project getProject() {
          return project;
        }

        @Override
        public ResourceType getType() {
          return ResourceType.ATTACHMENT;
        }

        @Override
        public Resource getContainer() {
          return container;
        }
      };
    } else {
      return new GlobalResource() {
        @Override
        public String getId() {
          return id.toString();
        }

        @Override
        public ResourceType getType() {
          return ResourceType.ATTACHMENT;
        }

        @Override
        public Resource getContainer() {
          return container;
        }
      };
    }
  }
Beispiel #12
0
 /**
  * 이 첨부 파일을 {@code to}로 옮긴다.
  *
  * @param to 첨부 파일이 새로 옮겨질 리소스
  * @return
  */
 public void moveTo(Resource to) {
   containerType = to.getType();
   containerId = to.getId();
   update();
 }
Beispiel #13
0
 /**
  * 주어진 {@code container}에 첨부된 첨부 파일의 갯수를 반환한다.
  *
  * <p>when:
  *
  * @param container 첨부 파일이 첨부된 리소스
  * @return 첨부 파일의 목록
  */
 public static int countByContainer(Resource container) {
   return find.where()
       .eq("containerType", container.getType())
       .eq("containerId", container.getId())
       .findRowCount();
 }
Beispiel #14
0
 /**
  * 주어진 {@code container}의 첨부된 첨부 파일의 목록을 반환한다.
  *
  * @param container 첨부 파일이 첨부된 리소스
  * @return 첨부 파일의 목록
  */
 public static List<Attachment> findByContainer(Resource container) {
   return findByContainer(container.getType(), container.getId());
 }