/** * Yona, 21st Century Project Hosting SW *

* Copyright Yona & Yobi Authors & NAVER Corp. & NAVER LABS Corp. * https://yona.io **/ package controllers; import com.avaje.ebean.ExpressionList; import com.avaje.ebean.Page; import controllers.annotation.AnonymousCheck; import controllers.annotation.GuestProhibit; import controllers.PullRequestApp.SearchCondition; import controllers.PullRequestApp.Category; import models.*; import models.enumeration.Operation; import models.enumeration.RequestState; import models.enumeration.RoleType; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import org.apache.commons.collections.CollectionUtils; import play.data.Form; import play.data.validation.Validation; import play.data.validation.ValidationError; import play.db.ebean.Transactional; import play.libs.Json; import play.mvc.Controller; import play.mvc.Http; import play.mvc.Result; import utils.*; import views.html.organization.create; import views.html.organization.deleteForm; import views.html.organization.members; import views.html.organization.setting; import views.html.organization.view; import views.html.organization.group_pullrequest_list; import javax.servlet.ServletException; import javax.validation.ConstraintViolation; import java.io.IOException; import java.security.NoSuchAlgorithmException; import java.util.*; import static play.data.Form.form; import static utils.LogoUtil.*; /** * @author Keeun Baik */ @AnonymousCheck public class OrganizationApp extends Controller { @AnonymousCheck(requiresLogin = false, displaysFlashMessage = true) public static Result organizationPullRequests(String organizationName, String category) { Organization organization = Organization.findByName(organizationName); if (organization == null) { return notFound(ErrorViews.NotFound.render("error.notfound.organization")); } SearchCondition condition = Form.form(SearchCondition.class).bindFromRequest().get(); if (category.equals("open")) { condition.setOrganization(organization).setCategory(Category.OPEN); } else { condition.setOrganization(organization).setCategory(Category.CLOSED); } Page page = PullRequest.findPagingList(condition); return ok(group_pullrequest_list.render("title.pullrequest", organization, page, condition, category)); } @AnonymousCheck(requiresLogin = false, displaysFlashMessage = true) public static Result organizationClosedPullRequests(String organizationName) { return organizationPullRequests(organizationName, "closed"); } /** * show New Group page * @return {@link Result} */ @AnonymousCheck(requiresLogin = true, displaysFlashMessage = true) public static Result newForm() { return ok(create.render("title.newOrganization", new Form<>(Organization.class))); } /** * create New Group * @return {@link Result} * @throws Exception */ @AnonymousCheck(requiresLogin = true, displaysFlashMessage = true) @GuestProhibit public static Result newOrganization() throws Exception { Form newOrgForm = form(Organization.class).bindFromRequest(); if (newOrgForm.hasErrors()) { play.Logger.warn("newOrgForm.errors().keySet() " + newOrgForm.error("name").messages()); flash(Constants.WARNING, newOrgForm.error("name").message()); return badRequest(create.render("title.newOrganization", newOrgForm)); } validate(newOrgForm); if (newOrgForm.hasErrors()) { flash(Constants.WARNING, newOrgForm.error("name").message()); return badRequest(create.render("title.newOrganization", newOrgForm)); } else { Organization org = newOrgForm.get(); org.created = new Date(); org.save(); UserApp.currentUser().createOrganization(org); return redirect(routes.OrganizationApp.organization(org.name)); } } private static void validate(Form newOrgForm) { Organization organization = newOrgForm.get(); play.Logger.error("org: " + organization.name); Set> results = Validation.getValidator().validate(newOrgForm.get()); if (!results.isEmpty()) { newOrgForm.reject("name", "organization.name.alert"); } String name = newOrgForm.field("name").value(); if (User.isLoginIdExist(name)) { newOrgForm.reject("name", "organization.name.duplicate"); } if (Organization.isNameExist(name)) { newOrgForm.reject("name", "organization.name.duplicate"); } } /** * show specific group's main page * @param organizationName group name * @return {@link Result} */ public static Result organization(String organizationName) { Organization org = Organization.findByName(organizationName); if (org == null) { return notFound(ErrorViews.NotFound.render("error.notfound.organization")); } return ok(view.render(org)); } @Transactional public static Result addMember(String organizationName) { Form addMemberForm = form(User.class).bindFromRequest(); Result result = validateForAddMember(addMemberForm, organizationName); if (result != null) { return result; } User targetUser = User.findByLoginId(addMemberForm.get().loginId); Organization organization = Organization.findByName(organizationName); OrganizationUser.assignRole(targetUser.id, organization.id, RoleType.ORG_MEMBER.roleType()); organization.cleanEnrolledUsers(); NotificationEvent.afterOrganizationMemberRequest(organization, targetUser, RequestState.ACCEPT); return redirect(routes.OrganizationApp.members(organizationName)); } private static Result validateForAddMember(Form addMemberForm, String organizationName) { String userLoginId = addMemberForm.get().loginId; User userToBeAdded = User.findByLoginId(userLoginId); if (addMemberForm.hasErrors() || userToBeAdded.isAnonymous()) { flash(Constants.WARNING, "organization.member.unknownUser"); return redirect(routes.OrganizationApp.members(organizationName)); } if (userToBeAdded.isGuest) { flash(Constants.WARNING, "error.forbidden.to.guest.user"); return redirect(routes.OrganizationApp.members(organizationName)); } Organization organization = Organization.findByName(organizationName); if (organization == null) { flash(Constants.WARNING, "organization.member.unknownOrganization"); return redirect(routes.OrganizationApp.members(organizationName)); } User currentUser = UserApp.currentUser(); if (!AccessControl.isAllowed(currentUser, organization.asResource(), Operation.UPDATE)) { flash(Constants.WARNING, "organization.member.needManagerRole"); return redirect(routes.OrganizationApp.members(organizationName)); } if (OrganizationUser.exist(organization.id, userToBeAdded.id)) { flash(Constants.WARNING, "organization.member.alreadyMember"); return redirect(routes.OrganizationApp.members(organizationName)); } return null; } @Transactional public static Result deleteMember(String organizationName, Long userId) { Result result = validateForDeleteMember(organizationName, userId); if (result != null) { return result; } Organization organization = Organization.findByName(organizationName); OrganizationUser.delete(organization.id, userId); if (UserApp.currentUser().id.equals(userId)) { return okWithLocation(routes.OrganizationApp.organization(organizationName).url()); } else { return okWithLocation(routes.OrganizationApp.members(organizationName).url()); } } private static Result validateForDeleteMember(String organizationName, Long userId) { Organization organization = Organization.findByName(organizationName); if (organization == null) { return notFound(ErrorViews.NotFound.render("organization.member.unknownOrganization", organization)); } if (!OrganizationUser.exist(organization.id, userId)) { flash(Constants.WARNING, "organization.member.isNotAMember"); return okWithLocation(routes.OrganizationApp.members(organizationName).url()); } User currentUser = UserApp.currentUser(); if (!AccessControl.isAllowed(currentUser, organization.asResource(), Operation.UPDATE) && !currentUser.id.equals(userId)) { flash(Constants.WARNING, "organization.member.needManagerRole"); return okWithLocation(routes.OrganizationApp.members(organizationName).url()); } if (OrganizationUser.isAdmin(organization.id, userId) && organization.getAdmins().size() == 1) { flash(Constants.WARNING, "organization.member.atLeastOneAdmin"); return okWithLocation(routes.OrganizationApp.members(organizationName).url()); } return null; } @Transactional public static Result editMember(String organizationName, Long userId) { Form roleForm = form(Role.class).bindFromRequest(); Result result = validateForEditMember(roleForm, organizationName, userId); if (result != null) { return result; } Organization organization = Organization.findByName(organizationName); OrganizationUser.assignRole(userId, organization.id, roleForm.get().id); return status(Http.Status.NO_CONTENT); } private static Result validateForEditMember(Form roleForm, String organizationName, Long userId) { if (roleForm.hasErrors()) { flash(Constants.WARNING, "organization.member.unknownRole"); return okWithLocation(routes.OrganizationApp.members(organizationName).url()); } Organization organization = Organization.findByName(organizationName); if (organization == null) { return notFound(ErrorViews.NotFound.render("organization.member.unknownOrganization", organization)); } if (!OrganizationUser.exist(organization.id, userId)) { flash(Constants.WARNING, "organization.member.isNotAMember"); return okWithLocation(routes.OrganizationApp.members(organizationName).url()); } User currentUser = UserApp.currentUser(); if (!AccessControl.isAllowed(currentUser, organization.asResource(), Operation.UPDATE)) { flash(Constants.WARNING, "organization.member.needManagerRole"); return okWithLocation(routes.OrganizationApp.members(organizationName).url()); } if (organization.isLastAdmin(User.find.byId(userId)) && roleForm.get().id.equals(RoleType.ORG_MEMBER.roleType())) { flash(Constants.WARNING, "organization.member.atLeastOneAdmin"); return okWithLocation(routes.OrganizationApp.members(organizationName).url()); } return null; } @Transactional @AnonymousCheck(requiresLogin = true, displaysFlashMessage = true) public static Result leave(String organizationName) { ValidationResult result = validateForLeave(organizationName); if (!result.hasError()) { OrganizationUser.delete(Organization.findByName(organizationName).id, UserApp.currentUser().id); } return result.getResult(); } public static ValidationResult validateForLeave(String organizationName) { Organization organization = Organization.findByName(organizationName); if (organization == null) { return new ValidationResult(notFound(getJsonErrorMsg("organization.member.unknownOrganization")), true); } if (!AccessControl.isAllowed(UserApp.currentUser(), organization.asResource(), Operation.LEAVE)) { if (OrganizationUser.findAdminsOf(organization).size() == 1) { return new ValidationResult(forbidden(getJsonErrorMsg("organization.member.atLeastOneAdmin")), true); } } return new ValidationResult(okWithLocation(routes.OrganizationApp.organization(organizationName).url()), false); } private static JsonNode getJsonErrorMsg(String errMsg) { Map response = new HashMap<>(); response.put("errorMsg", errMsg); return Json.toJson(response); } public static Result members(String organizationName) { Result result = validateForSetting(organizationName); if (result != null) { return result; } Organization organization = Organization.findByName(organizationName); return ok(members.render(organization, Role.findOrganizationRoles())); } private static Result validateForSetting(String organizationName) { Organization organization = Organization.findByName(organizationName); if (organization == null) { return notFound(ErrorViews.NotFound.render("organization.member.unknownOrganization", organization)); } User currentUser = UserApp.currentUser(); if (!AccessControl.isAllowed(currentUser, organization.asResource(), Operation.UPDATE)) { return forbidden(ErrorViews.Forbidden.render("error.forbidden", organization)); } return null; } public static Result settingForm(String organizationName) { Result result = validateForSetting(organizationName); if (result != null) { return result; } Organization organization = Organization.findByName(organizationName); return ok(setting.render(organization, form(Organization.class).fill(organization))); } private static Result okWithLocation(String location) { ObjectNode result = Json.newObject(); result.put("location", location); return ok(result); } /** * update group's info * @param organizationName group name * @return {@link Result} * @throws IOException * @throws NoSuchAlgorithmException */ public static Result updateOrganizationInfo(String organizationName) throws IOException, NoSuchAlgorithmException, ServletException { Form organizationForm = form(Organization.class).bindFromRequest(); Organization modifiedOrganization = organizationForm.get(); Result result = validateForUpdate(organizationForm, modifiedOrganization); if (result != null) { return result; } Http.MultipartFormData.FilePart filePart = request().body().asMultipartFormData() .getFile("logoPath"); if (!isEmptyFilePart(filePart)) { Attachment.deleteAll(modifiedOrganization.asResource()); new Attachment().store(filePart.getFile(), filePart.getFilename(), modifiedOrganization.asResource()); } Organization original = Organization.find.byId(modifiedOrganization.id); original.updateWith(modifiedOrganization); UserApp.currentUser().updateFavoriteOrganization(modifiedOrganization); FavoriteOrganization.updateFavoriteOrganization(modifiedOrganization); return redirect(routes.OrganizationApp.settingForm(modifiedOrganization.name)); } private static Result validateForUpdate(Form organizationForm, Organization modifiedOrganization) { Organization organization = Organization.find.byId(modifiedOrganization.id); if (organization == null) { return notFound(ErrorViews.NotFound.render("organization.member.unknownOrganization")); } if (!AccessControl.isAllowed(UserApp.currentUser(), organization.asResource(), Operation.UPDATE)) { flash(Constants.WARNING, "organization.member.needManagerRole"); return forbidden(ErrorViews.Forbidden.render("error.forbidden", organization)); } if (isDuplicateName(organization, modifiedOrganization)) { organizationForm.reject("name", "organization.name.duplicate"); return badRequest(setting.render(organization, organizationForm)); } Http.MultipartFormData.FilePart filePart = request().body().asMultipartFormData() .getFile("logoPath"); if (!isEmptyFilePart(filePart)) { if (!isImageFile(filePart.getFilename())) { flash(Constants.WARNING, "project.logo.alert"); organizationForm.reject("logoPath"); } if (filePart.getFile().length() > LOGO_FILE_LIMIT_SIZE) { flash(Constants.WARNING, "project.logo.fileSizeAlert"); organizationForm.reject("logoPath"); } } if (organizationForm.hasErrors()) { return badRequest(setting.render(organization, organizationForm)); } return null; } private static boolean isDuplicateName(Organization organization, Organization modifiedOrganization) { if (isNotChangedName(organization.name, modifiedOrganization.name)) { return false; } if (User.isLoginIdExist(modifiedOrganization.name)) { return true; } if (Organization.isNameExist(modifiedOrganization.name)) { return true; } return false; } private static boolean isNotChangedName(String name, String modifiedName) { return name.equals(modifiedName); } public static Result deleteForm(String organizationName) { Result result = validateForSetting(organizationName); if (result != null) { return result; } Organization organization = Organization.findByName(organizationName); return ok(deleteForm.render(organization)); } @Transactional public static Result deleteOrganization(String organizationName) { Organization organization = Organization.findByName(organizationName); ValidationResult result = validateForDelete(organization); if (result.hasError()) { return result.getResult(); } organization.delete(); return redirect(routes.Application.index()); } private static ValidationResult validateForDelete(Organization organization) { if (organization == null) { return new ValidationResult(notFound(getJsonErrorMsg("organization.member.unknownOrganization")), true); } if (!AccessControl.isAllowed(UserApp.currentUser(), organization.asResource(), Operation.DELETE)) { return new ValidationResult(notFound(getJsonErrorMsg("organization.member.needManagerRole")), true); } if (organization.projects != null && organization.projects.size() > 0) { return new ValidationResult(notFound(getJsonErrorMsg("organization.delete.impossible.project.exist")), true); } return new ValidationResult(okWithLocation(routes.OrganizationApp.organization(organization.name).url()), false); } @GuestProhibit public static Result orgList(String query, int pageNum){ if(Application.HIDE_PROJECT_LISTING){ return forbidden(ErrorViews.Forbidden.render("error.auth.unauthorized.waringMessage")); } if (pageNum < 1) { return notFound(ErrorViews.NotFound.render("error.notfound")); } Page orgs = Organization.findByNameLike(query).getPage(pageNum-1); return ok(views.html.organization.list.render("title.projectList", orgs, query)); } }