expands logging capability

Closes #120
This commit is contained in:
Jake Potrebic 2020-09-09 20:33:44 -07:00
parent 8b53dc18a0
commit 0ff4ff0687
No known key found for this signature in database
GPG Key ID: 7C58557EC9C421F8
15 changed files with 183 additions and 65 deletions

View File

@ -6,7 +6,30 @@ create type role_category as enum ('global', 'project', 'organization');
alter type role_category owner to hangar;
create type logged_action_type as enum ('project_visibility_change', 'project_renamed', 'project_flagged', 'project_settings_changed', 'project_member_removed', 'project_icon_changed', 'project_page_edited', 'project_flag_resolved', 'version_deleted', 'version_uploaded', 'version_description_changed', 'version_review_state_changed', 'user_tagline_changed');
create type logged_action_type as enum (
'project_visibility_change',
'project_renamed',
'project_flagged',
'project_settings_changed',
'project_member_removed',
'project_icon_changed',
'project_flag_resolved',
'project_page_created',
'project_page_deleted',
'project_page_edited',
'version_deleted',
'version_uploaded',
'version_description_changed',
'version_review_state_changed',
'user_tagline_changed',
'user_locked',
'user_unlocked',
'user_apikey_create',
'user_apikey_delete',
'org_members_added',
'org_member_removed',
'org_member_roles_updated'
);
alter type logged_action_type owner to hangar;

View File

@ -6,7 +6,30 @@ create type role_category as enum ('global', 'project', 'organization');
alter type role_category owner to hangar;
create type logged_action_type as enum ('project_visibility_change', 'project_renamed', 'project_flagged', 'project_settings_changed', 'project_member_removed', 'project_icon_changed', 'project_page_edited', 'project_flag_resolved', 'version_deleted', 'version_uploaded', 'version_description_changed', 'version_review_state_changed', 'user_tagline_changed');
create type logged_action_type as enum (
'project_visibility_change',
'project_renamed',
'project_flagged',
'project_settings_changed',
'project_member_removed',
'project_icon_changed',
'project_flag_resolved',
'project_page_created',
'project_page_deleted',
'project_page_edited',
'version_deleted',
'version_uploaded',
'version_description_changed',
'version_review_state_changed',
'user_tagline_changed',
'user_locked',
'user_unlocked',
'user_apikey_create',
'user_apikey_delete',
'org_members_added',
'org_member_removed',
'org_member_roles_updated'
);
alter type logged_action_type owner to hangar;

View File

@ -2,6 +2,8 @@ package io.papermc.hangar.controller;
import com.fasterxml.jackson.core.JsonProcessingException;
import io.papermc.hangar.config.hangar.HangarConfig;
import io.papermc.hangar.db.customtypes.LoggedActionType;
import io.papermc.hangar.db.customtypes.LoggedActionType.OrganizationContext;
import io.papermc.hangar.db.model.OrganizationsTable;
import io.papermc.hangar.db.model.UserOrganizationRolesTable;
import io.papermc.hangar.db.model.UsersTable;
@ -16,6 +18,7 @@ import io.papermc.hangar.service.NotificationService;
import io.papermc.hangar.service.OrgFactory;
import io.papermc.hangar.service.OrgService;
import io.papermc.hangar.service.RoleService;
import io.papermc.hangar.service.UserActionLogService;
import io.papermc.hangar.service.UserService;
import io.papermc.hangar.util.AlertUtil;
import io.papermc.hangar.util.AlertUtil.AlertType;
@ -36,7 +39,9 @@ import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.servlet.http.HttpServletRequest;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -53,18 +58,23 @@ public class OrgController extends HangarController {
private final OrgFactory orgFactory;
private final UserService userService;
private final RoleService roleService;
private final UserActionLogService userActionLogService;
private final NotificationService notificationService;
private final HangarConfig hangarConfig;
private final HttpServletRequest request;
@Autowired
public OrgController(AuthenticationService authenticationService, OrgService orgService, OrgFactory orgFactory, UserService userService, RoleService roleService, NotificationService notificationService, HangarConfig hangarConfig) {
public OrgController(AuthenticationService authenticationService, OrgService orgService, OrgFactory orgFactory, UserService userService, RoleService roleService, UserActionLogService userActionLogService, NotificationService notificationService, HangarConfig hangarConfig, HttpServletRequest request) {
this.authenticationService = authenticationService;
this.orgService = orgService;
this.orgFactory = orgFactory;
this.userService = userService;
this.roleService = roleService;
this.userActionLogService = userActionLogService;
this.notificationService = notificationService;
this.hangarConfig = hangarConfig;
this.request = request;
}
@Secured("ROLE_USER")
@ -107,10 +117,11 @@ public class OrgController extends HangarController {
@Secured("ROLE_USER")
@PostMapping(value = "/organizations/new", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public ModelAndView create(@RequestParam String name, @RequestParam(required = false) List<Long> users, @RequestParam(required = false) List<Role> roles, RedirectAttributes attributes) {
UsersTable currUser = getCurrentUser();
if (orgService.getUserOwnedOrgs(getCurrentUser().getId()).size() >= hangarConfig.org.getCreateLimit()) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "at create limit");
}
if (currentUser.get().get().isLocked()) throw new ResponseStatusException(HttpStatus.BAD_REQUEST);
if (currUser.isLocked()) throw new ResponseStatusException(HttpStatus.BAD_REQUEST);
if (!hangarConfig.org.isEnabled()) {
AlertUtil.showAlert(attributes, AlertType.ERROR, "error.org.disabled");
return Routes.ORG_SHOW_CREATOR.getRedirect();
@ -119,7 +130,7 @@ public class OrgController extends HangarController {
OrganizationsTable org;
try {
org = orgFactory.createOrganization(name, currentUser.get().get().getId(), userRoles);
org = orgFactory.createOrganization(name, currUser.getId(), userRoles);
} catch (HangarException e) {
AlertUtil.showAlert(attributes, AlertType.ERROR, e.getMessageKey(), e.getArgs());
return Routes.ORG_SHOW_CREATOR.getRedirect();
@ -134,7 +145,7 @@ public class OrgController extends HangarController {
public ModelAndView updateAvatar(@PathVariable String organization) {
try {
URI uri = authenticationService.changeAvatarUri(getCurrentUser().getName(), organization);
return new ModelAndView("redirect:" + uri.toString());
return Routes.getRedirectToUrl(uri.toString());
} catch (JsonProcessingException e) {
ModelAndView mav = Routes.USERS_SHOW_PROJECTS.getRedirect(organization);
AlertUtil.showAlert(mav, AlertType.ERROR, "organization.avatarFailed");
@ -152,16 +163,26 @@ public class OrgController extends HangarController {
@RequestParam(required = false) List<Role> roleUps) {
OrganizationsTable org = orgService.getOrganization(organization); // Won't be null because the PreAuth check should catch that
Map<Long, Role> userRoles = zip(users, roles);
List<String> newState = new ArrayList<>();
userRoles.forEach((memberId, role) -> {
UsersTable memberUser = userService.getUsersTable(memberId);
newState.add(memberUser.getName() + ": " + role.getTitle());
roleService.addOrgMemberRole(org.getId(), memberId, role, false);
notificationService.sendNotification(memberId, org.getId(), NotificationType.ORGANIZATION_INVITE, new String[]{"notification.organization.invite", role.getTitle(), org.getName()});
});
if (!newState.isEmpty()) {
userActionLogService.organization(request, LoggedActionType.ORG_MEMBERS_ADDED.with(OrganizationContext.of(org.getId())), String.join("<br>", newState), "");
newState.clear();
}
Map<UsersTable, Role> userRoleUpdates = zip(userService.getUsers(userUps), roleUps);
userRoleUpdates.forEach((user, role) -> {
newState.add(user.getName() + ": " + role.getTitle());
roleService.updateRole(org, user.getId(), role);
});
// TODO user action logging for orgs
if (!newState.isEmpty()) {
userActionLogService.organization(request, LoggedActionType.ORG_MEMBER_ROLES_UPDATED.with(OrganizationContext.of(org.getId())), String.join("<br>", newState), "");
}
return Routes.USERS_SHOW_PROJECTS.getRedirect(organization);
}
@ -172,7 +193,7 @@ public class OrgController extends HangarController {
OrganizationsTable org = orgService.getOrganization(organization);
UserData user = userService.getUserData(username);
if (roleService.removeMember(org, user.getUser().getId()) != 0) {
// TODO org logging
userActionLogService.organization(request, LoggedActionType.ORG_MEMBER_REMOVED.with(OrganizationContext.of(org.getId())), "Removed " + user.getUser().getName(), "");
}
return Routes.USERS_SHOW_PROJECTS.getRedirect(organization);
}

View File

@ -108,6 +108,7 @@ public class PagesController extends HangarController {
if (!projectPage.getIsDeletable()) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Cannot delete this page");
}
userActionLogService.projectPage(request, LoggedActionType.PROJECT_PAGE_DELETED.with(ProjectPageContext.of(projectPage.getProjectId(), projectPage.getId())), "", projectPage.getContents());
projectPageDao.get().delete(projectPage);
return Routes.PROJECTS_SHOW.getRedirect(author, slug);
}
@ -136,14 +137,15 @@ public class PagesController extends HangarController {
parentIdLong = null;
}
projectPage = pagesFactory.createPage(pageContent, newPageName, pageName, parentIdLong, project.getId());
userActionLogService.projectPage(request, LoggedActionType.PROJECT_PAGE_CREATED.with(ProjectPageContext.of(project.getId(), projectPage.getId())), pageContent, "");
toReturn = new ResponseEntity<>(HttpStatus.OK); // redirect handled by pageEdit.js
} else {
oldContents = projectPage.getContents();
projectPage.setContents(pageContent);
projectPageDao.get().update(projectPage);
userActionLogService.projectPage(request, LoggedActionType.PROJECT_PAGE_EDITED.with(ProjectPageContext.of(project.getId(), projectPage.getId())), pageContent, oldContents);
toReturn = Routes.PAGES_SHOW.getRedirect(author, slug, StringUtils.slugify(pageName));
}
userActionLogService.projectPage(request, LoggedActionType.PROJECT_PAGE_EDITED.with(ProjectPageContext.of(project.getId(), projectPage.getId())), pageContent, oldContents);
return toReturn;
}

View File

@ -650,8 +650,9 @@ public class VersionsController extends HangarController {
@Secured("ROLE_USER")
@PostMapping(value = "/{author}/{slug}/versions/{version}/restore", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public ModelAndView restore(@PathVariable String author, @PathVariable String slug, @PathVariable String version, @RequestParam String comment) {
versionService.changeVisibility(versionData.get(), Visibility.PUBLIC, comment, getCurrentUser().getId());
userActionLogService.version(request, LoggedActionType.VERSION_DELETED.with(VersionContext.of(versionData.get().getP().getProject().getId(), versionData.get().getV().getId())), "Restore: " + comment, "");
VersionData vData = versionData.get();
versionService.changeVisibility(vData, Visibility.PUBLIC, comment, getCurrentUser().getId());
userActionLogService.version(request, LoggedActionType.VERSION_DELETED.with(VersionContext.of(vData.getP().getProject().getId(), vData.getV().getId())), "Restore: " + comment, "");
return Routes.VERSIONS_SHOW.getRedirect(author, slug, version);
}
@ -660,11 +661,12 @@ public class VersionsController extends HangarController {
@Secured("ROLE_USER")
@PostMapping(value = "/{author}/{slug}/versions/{version}/save", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public ModelAndView saveDescription(@PathVariable String author, @PathVariable String slug, @PathVariable String version, @RequestParam String content) {
String oldDesc = versionData.get().getV().getDescription();
VersionData vData = versionData.get();
String oldDesc = vData.getV().getDescription();
String newDesc = content.trim();
versionData.get().getV().setDescription(newDesc);
versionService.update(versionData.get().getV());
userActionLogService.version(request, LoggedActionType.VERSION_DESCRIPTION_CHANGED.with(VersionContext.of(versionData.get().getP().getProject().getId(), versionData.get().getV().getId())), newDesc, oldDesc);
vData.getV().setDescription(newDesc);
versionService.update(vData.getV());
userActionLogService.version(request, LoggedActionType.VERSION_DESCRIPTION_CHANGED.with(VersionContext.of(vData.getP().getProject().getId(), vData.getV().getId())), newDesc, oldDesc);
return Routes.VERSIONS_SHOW.getRedirect(author, slug, version);
}

View File

@ -1,11 +1,14 @@
package io.papermc.hangar.controller.api;
import io.papermc.hangar.db.customtypes.LoggedActionType;
import io.papermc.hangar.db.customtypes.LoggedActionType.UserContext;
import io.papermc.hangar.model.ApiAuthInfo;
import io.papermc.hangar.model.NamedPermission;
import io.papermc.hangar.model.Permission;
import io.papermc.hangar.model.generated.ApiKeyRequest;
import io.papermc.hangar.model.generated.ApiKeyResponse;
import io.papermc.hangar.service.ApiKeyService;
import io.papermc.hangar.service.UserActionLogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
@ -13,20 +16,26 @@ import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.server.ResponseStatusException;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
@Controller
public class KeysApiController implements KeysApi {
private final ApiKeyService apiKeyService;
private final UserActionLogService userActionLogService;
private final ApiAuthInfo apiAuthInfo;
private final HttpServletRequest request;
@Autowired
public KeysApiController(ApiKeyService apiKeyService, ApiAuthInfo apiAuthInfo) {
public KeysApiController(ApiKeyService apiKeyService, UserActionLogService userActionLogService, ApiAuthInfo apiAuthInfo, HttpServletRequest request) {
this.apiKeyService = apiKeyService;
this.userActionLogService = userActionLogService;
this.apiAuthInfo = apiAuthInfo;
this.request = request;
}
@Override
@ -58,6 +67,7 @@ public class KeysApiController implements KeysApi {
}
apiKeyService.createApiKey(body.getName(), userId, tokenIdentifier, token, perm);
userActionLogService.user(request, LoggedActionType.USER_APIKEY_CREATE.with(UserContext.of(apiAuthInfo.getUserId())), "Key Name: " + body.getName() + "<br>" + perms.stream().map(NamedPermission::getFrontendName).collect(Collectors.joining(",<br>")), "");
return ResponseEntity.ok(new ApiKeyResponse().perms(perm.toNamed()).key(tokenIdentifier + "." + token));
}
@ -71,6 +81,7 @@ public class KeysApiController implements KeysApi {
if (rowsAffected == 0) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND);
} else {
userActionLogService.user(request, LoggedActionType.USER_APIKEY_DELETE.with(UserContext.of(apiAuthInfo.getUserId())), "", "Key Name: " + name);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
}

View File

@ -12,13 +12,26 @@ public class LoggedAction extends PGobject {
public static final LoggedAction PROJECT_SETTINGS_CHANGED = new LoggedAction("project_settings_changed");
public static final LoggedAction PROJECT_MEMBER_REMOVED = new LoggedAction("project_member_removed");
public static final LoggedAction PROJECT_ICON_CHANGED = new LoggedAction("project_icon_changed");
public static final LoggedAction PROJECT_PAGE_EDITED = new LoggedAction("project_page_edited");
public static final LoggedAction PROJECT_FLAG_RESOLVED = new LoggedAction("project_flag_resolved");
public static final LoggedAction PROJECT_PAGE_CREATED = new LoggedAction("project_page_created");
public static final LoggedAction PROJECT_PAGE_DELETED = new LoggedAction("project_page_deleted");
public static final LoggedAction PROJECT_PAGE_EDITED = new LoggedAction("project_page_edited");
public static final LoggedAction VERSION_DELETED = new LoggedAction("version_deleted");
public static final LoggedAction VERSION_UPLOADED = new LoggedAction("version_uploaded");
public static final LoggedAction VERSION_DESCRIPTION_CHANGED = new LoggedAction("version_description_changed");
public static final LoggedAction VERSION_REVIEW_STATE_CHANGED = new LoggedAction("version_review_state_changed");
public static final LoggedAction USER_TAGLINE_CHANGED = new LoggedAction("user_tagline_changed");
public static final LoggedAction USER_LOCKED = new LoggedAction("user_locked");
public static final LoggedAction USER_UNLOCKED = new LoggedAction("user_unlocked");
public static final LoggedAction USER_APIKEY_CREATE = new LoggedAction("user_apikey_create");
public static final LoggedAction USER_APIKEY_DELETE = new LoggedAction("user_apikey_delete");
public static final LoggedAction ORG_MEMBERS_ADDED = new LoggedAction("org_members_added");
public static final LoggedAction ORG_MEMBER_REMOVED = new LoggedAction("org_member_removed");
public static final LoggedAction ORG_MEMBER_ROLES_UPDATED = new LoggedAction("org_member_roles_updated");
private String value;

View File

@ -16,14 +16,26 @@ public class LoggedActionType<C extends AbstractContext<C>> {
public static final LoggedActionType<ProjectContext> PROJECT_SETTINGS_CHANGED = new LoggedActionType<>(LoggedAction.PROJECT_SETTINGS_CHANGED, "ProjectSettingsChanged", "The project settings were changed");
public static final LoggedActionType<ProjectContext> PROJECT_MEMBER_REMOVED = new LoggedActionType<>(LoggedAction.PROJECT_MEMBER_REMOVED, "ProjectMemberRemoved", "A Member was removed from the project");
public static final LoggedActionType<ProjectContext> PROJECT_ICON_CHANGED = new LoggedActionType<>(LoggedAction.PROJECT_ICON_CHANGED, "ProjectIconChanged", "The project icon was changed");
public static final LoggedActionType<ProjectPageContext> PROJECT_PAGE_EDITED = new LoggedActionType<>(LoggedAction.PROJECT_PAGE_EDITED, "ProjectPageEdited", "A project page got edited");
public static final LoggedActionType<ProjectContext> PROJECT_FLAG_RESOLVED = new LoggedActionType<>(LoggedAction.PROJECT_FLAG_RESOLVED, "ProjectFlagResolved", "The flag was resolved");
public static final LoggedActionType<ProjectPageContext> PROJECT_PAGE_CREATED = new LoggedActionType<>(LoggedAction.PROJECT_PAGE_CREATED, "ProjectPageCreated", "A project page was created");
public static final LoggedActionType<ProjectPageContext> PROJECT_PAGE_DELETED = new LoggedActionType<>(LoggedAction.PROJECT_PAGE_DELETED, "ProjectPageDeleted", "A project page was deleted");
public static final LoggedActionType<ProjectPageContext> PROJECT_PAGE_EDITED = new LoggedActionType<>(LoggedAction.PROJECT_PAGE_EDITED, "ProjectPageEdited", "A project page was edited");
public static final LoggedActionType<VersionContext> VERSION_DELETED = new LoggedActionType<>(LoggedAction.VERSION_DELETED, "VersionDeleted", "The version was deleted");
public static final LoggedActionType<VersionContext> VERSION_UPLOADED = new LoggedActionType<>(LoggedAction.VERSION_UPLOADED, "VersionUploaded", "A new version was uploaded");
public static final LoggedActionType<VersionContext> VERSION_DESCRIPTION_CHANGED = new LoggedActionType<>(LoggedAction.VERSION_DESCRIPTION_CHANGED, "VersionDescriptionEdited", "The version description was edited");
public static final LoggedActionType<VersionContext> VERSION_REVIEW_STATE_CHANGED = new LoggedActionType<>(LoggedAction.VERSION_REVIEW_STATE_CHANGED, "VersionReviewStateChanged", "If the review state changed");
public static final LoggedActionType<UserContext> USER_TAGLINE_CHANGED = new LoggedActionType<>(LoggedAction.USER_TAGLINE_CHANGED, "UserTaglineChanged", "The user tagline changed");
public static final LoggedActionType<VersionContext> VERSION_REVIEW_STATE_CHANGED = new LoggedActionType<>(LoggedAction.VERSION_REVIEW_STATE_CHANGED, "VersionReviewStateChanged", "The versions review state was changed");
public static final LoggedActionType<UserContext> USER_TAGLINE_CHANGED = new LoggedActionType<>(LoggedAction.USER_TAGLINE_CHANGED, "UserTaglineChanged", "The user tagline changed");
public static final LoggedActionType<UserContext> USER_LOCKED = new LoggedActionType<>(LoggedAction.USER_LOCKED, "UserLocked", "This user is locked");
public static final LoggedActionType<UserContext> USER_UNLOCKED = new LoggedActionType<>(LoggedAction.USER_UNLOCKED, "UserUnlocked", "This use is unlocked");
public static final LoggedActionType<UserContext> USER_APIKEY_CREATE = new LoggedActionType<>(LoggedAction.USER_APIKEY_CREATE, "UserApikeyCreate", "An apikey was created");
public static final LoggedActionType<UserContext> USER_APIKEY_DELETE = new LoggedActionType<>(LoggedAction.USER_APIKEY_DELETE, "UserApikeyDelete", "An apikey was deleted");
public static final LoggedActionType<OrganizationContext> ORG_MEMBERS_ADDED = new LoggedActionType<>(LoggedAction.ORG_MEMBERS_ADDED, "OrganizationMembersAdded", "Users were added to an organization");
public static final LoggedActionType<OrganizationContext> ORG_MEMBER_REMOVED = new LoggedActionType<>(LoggedAction.ORG_MEMBER_REMOVED, "OrganizationMemberRemoved", "User was removed from an organization");
public static final LoggedActionType<OrganizationContext> ORG_MEMBER_ROLES_UPDATED = new LoggedActionType<>(LoggedAction.ORG_MEMBER_ROLES_UPDATED, "OrganizationMemberRolesUpdated", "Organization members roles were updated");
private final LoggedAction value;
private final String name;
@ -157,7 +169,7 @@ public class LoggedActionType<C extends AbstractContext<C>> {
this.orgId = orgId;
}
public long getOrganizationLog() {
public long getOrganizationId() {
return orgId;
}

View File

@ -6,13 +6,10 @@ import io.papermc.hangar.db.model.LoggedActionsPageTable;
import io.papermc.hangar.db.model.LoggedActionsProjectTable;
import io.papermc.hangar.db.model.LoggedActionsUserTable;
import io.papermc.hangar.db.model.LoggedActionsVersionTable;
import io.papermc.hangar.model.viewhelpers.LoggedActionViewModel;
import org.jdbi.v3.sqlobject.config.RegisterBeanMapper;
import org.jdbi.v3.sqlobject.config.RegisterColumnMapper;
import org.jdbi.v3.sqlobject.config.RegisterRowMapper;
import org.jdbi.v3.sqlobject.customizer.BindBean;
import org.jdbi.v3.sqlobject.customizer.Define;
import org.jdbi.v3.sqlobject.customizer.DefineNamedBindings;
import org.jdbi.v3.sqlobject.customizer.Timestamped;
import org.jdbi.v3.sqlobject.statement.SqlQuery;
@ -54,12 +51,13 @@ public interface ActionsDao {
@RegisterRowMapper(LoggedActionViewModelMapper.class)
@SqlQuery("SELECT * FROM v_logged_actions la " +
" WHERE true " +
"<if(userFilter)>AND la.user_name = '<userFilter>'<endif> " +
"<if(projectFilter)>AND la.p_plugin_id = '<projectFilter>'<endif> " +
"<if(versionFilter)>AND la.pv_version_string = '<versionFilter>'<endif> " +
"<if(pageFilter)>AND la.pp_id = '<pageFilter>'<endif> " +
"<if(actionFilter)>AND la.action = '<actionFilter>'::LOGGED_ACTION_TYPE<endif> " +
"<if(subjectFilter)>AND la.s_name = '<subjectFilter>'<endif> " +
"ORDER BY la.created_at DESC OFFSET <offset> LIMIT <pageSize>")
List<LoggedActionViewModel<?>> getLog(@Define String userFilter, @Define String projectFilter, @Define String versionFilter, @Define String pageFilter, @Define String actionFilter, @Define String subjectFilter, @Define long offset, @Define long pageSize);
"<if(userFilter)>AND la.user_name = :userFilter<endif> " +
"<if(projectFilter)>AND la.p_plugin_id = :projectFilter<endif> " +
"<if(versionFilter)>AND la.pv_version_string = :versionFilter<endif> " +
"<if(pageFilter)>AND la.pp_id = :pageFilter<endif> " +
"<if(actionFilter)>AND la.action = :actionFilter::LOGGED_ACTION_TYPE<endif> " +
"<if(subjectFilter)>AND la.s_name = :subjectFilter<endif> " +
"ORDER BY la.created_at DESC OFFSET :offset LIMIT :pageSize")
@DefineNamedBindings
List<LoggedActionViewModel<?>> getLog(String userFilter, String projectFilter, String versionFilter, String pageFilter, String actionFilter, String subjectFilter, long offset, long pageSize);
}

View File

@ -40,8 +40,8 @@ public interface ProjectsApiDao {
" COALESCE(p.last_updated, p.created_at) AS last_updated," +
" p.visibility, " +
" <if(requesterId)> " +
" EXISTS(SELECT * FROM project_stars s WHERE s.project_id = p.id AND s.user_id = <requesterId>) AS user_starred, " +
" EXISTS(SELECT * FROM project_watchers s WHERE s.project_id = p.id AND s.user_id = <requesterId>) AS user_watching, " +
" EXISTS(SELECT * FROM project_stars s WHERE s.project_id = p.id AND s.user_id = :requesterId) AS user_starred, " +
" EXISTS(SELECT * FROM project_watchers s WHERE s.project_id = p.id AND s.user_id = :requesterId) AS user_watching, " +
" <endif>" +
" ps.homepage," +
" ps.issues," +
@ -53,22 +53,22 @@ public interface ProjectsApiDao {
" FROM home_projects p" +
" JOIN projects ps ON p.id = ps.id" +
" WHERE true " + //Not sure how else to get here a single Where
" <if(pluginId)> AND (p.plugin_id = '<pluginId>') <endif> " +
" <if(pluginId)> AND (p.plugin_id = :pluginId) <endif> " +
" <if(owner)> AND (p.owner_name = :owner) <endif> " +
" <if(!seeHidden)> AND (p.visibility = 0 <if(requesterId)>OR (<requesterId> = ANY(p.project_members) AND p.visibility != 4)<endif>) <endif> " +
" <if(!seeHidden)> AND (p.visibility = 0 <if(requesterId)>OR (:requesterId = ANY(p.project_members) AND p.visibility != 4)<endif>) <endif> " +
" <if(categories)> AND (p.category in (<categories>)) <endif> " +
" <if(query)> AND ( <queryStatement> ) <endif> " +
" <if(query)> AND ( :queryStatement ) <endif> " +
" <if(tags)> AND EXISTS ( SELECT pv.tag_name FROM jsonb_to_recordset(p.promoted_versions) " +
" AS pv(tag_name TEXT, tag_version TEXT) WHERE (pv.tag_name) in (<tags>) ) <endif> " +
" <if(orderBy)>ORDER BY <orderBy><endif> " +
" <if(orderBy)>ORDER BY :orderBy<endif> " +
" LIMIT :limit" +
" OFFSET :offset")
@RegisterColumnMapper(PromotedVersionMapper.class)
// @DefineNamedBindings
List<Project> listProjects(@Define String pluginId, @Define String owner, @Define boolean seeHidden, @Define Long requesterId, @Define String orderBy,
@DefineNamedBindings
List<Project> listProjects(String pluginId, String owner, @Define boolean seeHidden, Long requesterId, String orderBy,
@BindList(onEmpty = BindList.EmptyHandling.NULL_VALUE) List<Integer> categories,
@BindList(onEmpty = BindList.EmptyHandling.NULL_VALUE) List<String> tags, //TODO: implement tags with mc_version('data')
@Define String query, @Define String queryStatement, long limit, long offset);
@Define String query, String queryStatement, long limit, long offset);
@UseStringTemplateEngine
@SqlQuery("SELECT COUNT(*) FROM ( SELECT p.created_at," +

View File

@ -56,6 +56,10 @@ public enum NamedPermission {
this.frontendName = frontendName;
}
public String getFrontendName() {
return frontendName;
}
@Override
@JsonValue
public String toString() {

View File

@ -3,6 +3,8 @@ package io.papermc.hangar.service;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.papermc.hangar.config.hangar.HangarConfig;
import io.papermc.hangar.db.customtypes.LoggedActionType;
import io.papermc.hangar.db.customtypes.LoggedActionType.OrganizationContext;
import io.papermc.hangar.db.dao.HangarDao;
import io.papermc.hangar.db.dao.OrganizationDao;
import io.papermc.hangar.db.dao.UserDao;
@ -22,6 +24,8 @@ import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@ -35,19 +39,24 @@ public class OrgFactory {
private final UserService userService;
private final RoleService roleService;
private final NotificationService notificationService;
private final UserActionLogService userActionLogService;
private final RestTemplate restTemplate;
private final ObjectMapper mapper;
private final HttpServletRequest request;
@Autowired
public OrgFactory(HangarDao<OrganizationDao> organizationDao, HangarDao<UserDao> userDao, HangarConfig hangarConfig, UserService userService, RoleService roleService, NotificationService notificationService, RestTemplate restTemplate, ObjectMapper mapper) {
public OrgFactory(HangarDao<OrganizationDao> organizationDao, HangarDao<UserDao> userDao, HangarConfig hangarConfig, UserService userService, RoleService roleService, NotificationService notificationService, UserActionLogService userActionLogService, RestTemplate restTemplate, ObjectMapper mapper, HttpServletRequest request) {
this.organizationDao = organizationDao;
this.userDao = userDao;
this.hangarConfig = hangarConfig;
this.userService = userService;
this.roleService = roleService;
this.notificationService = notificationService;
this.userActionLogService = userActionLogService;
this.restTemplate = restTemplate;
this.mapper = mapper;
this.request = request;
}
public OrganizationsTable createOrganization(String name, long ownerId, Map<Long, Role> members) {
@ -83,11 +92,15 @@ public class OrgFactory {
UserData orgUser = userService.getUserData(authOrgUser.getId());
roleService.addGlobalRole(orgUser.getUser().getId(), Role.ORGANIZATION.getRoleId());
roleService.addOrgMemberRole(orgId, ownerId, Role.ORGANIZATION_OWNER, true);
List<String> newState = new ArrayList<>();
// TODO user action logging for org members
members.forEach((memberId, role) -> {
UsersTable memberUser = userDao.get().getById(memberId);
newState.add(memberUser.getName() + ": " + role.getTitle());
roleService.addOrgMemberRole(orgId, memberId, role, false);
notificationService.sendNotification(memberId, orgId, NotificationType.ORGANIZATION_INVITE, new String[]{"notification.organization.invite", role.getTitle(), name});
});
userActionLogService.organization(request, LoggedActionType.ORG_MEMBERS_ADDED.with(OrganizationContext.of(orgId)), String.join("<br>", newState), "<i>No Members</i>");
return org;
}
}

View File

@ -9,12 +9,11 @@ import io.papermc.hangar.db.model.LoggedActionsProjectTable;
import io.papermc.hangar.db.model.LoggedActionsUserTable;
import io.papermc.hangar.db.model.LoggedActionsVersionTable;
import io.papermc.hangar.model.viewhelpers.LoggedActionViewModel;
import io.papermc.hangar.util.RequestUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.List;
import java.util.Objects;
@ -22,18 +21,16 @@ import java.util.Objects;
public class UserActionLogService extends HangarService {
private final HangarDao<ActionsDao> actionsDao;
private final UserService userService;
@Autowired
public UserActionLogService(HangarDao<ActionsDao> actionsDao, UserService userService) {
public UserActionLogService(HangarDao<ActionsDao> actionsDao) {
this.actionsDao = actionsDao;
this.userService = userService;
}
public void project(HttpServletRequest request, LoggedActionType<LoggedActionType.ProjectContext> loggedActionType, String newState, String oldState) {
LoggedActionsProjectTable log = new LoggedActionsProjectTable(
getCurrentUser().getId(),
getInetAddress(request),
RequestUtil.getRemoteInetAddress(request),
loggedActionType.getValue(),
loggedActionType.getActionContext().getProjectId(),
newState,
@ -45,7 +42,7 @@ public class UserActionLogService extends HangarService {
public void projectPage(HttpServletRequest request, LoggedActionType<LoggedActionType.ProjectPageContext> loggedActionType, String newState, String oldState) {
LoggedActionsPageTable log = new LoggedActionsPageTable(
getCurrentUser().getId(),
getInetAddress(request),
RequestUtil.getRemoteInetAddress(request),
loggedActionType.getValue(),
loggedActionType.getActionContext().getProjectId(),
loggedActionType.getActionContext().getPageId(),
@ -58,7 +55,7 @@ public class UserActionLogService extends HangarService {
public void version(HttpServletRequest request, LoggedActionType<LoggedActionType.VersionContext> loggedActionType, String newState, String oldState) {
LoggedActionsVersionTable log = new LoggedActionsVersionTable(
getCurrentUser().getId(),
getInetAddress(request),
RequestUtil.getRemoteInetAddress(request),
loggedActionType.getValue(),
loggedActionType.getActionContext().getProjectId(),
loggedActionType.getActionContext().getVersionId(),
@ -71,7 +68,7 @@ public class UserActionLogService extends HangarService {
public void user(HttpServletRequest request, LoggedActionType<LoggedActionType.UserContext> loggedActionType, String newState, String oldState) {
LoggedActionsUserTable log = new LoggedActionsUserTable(
getCurrentUser().getId(),
getInetAddress(request),
RequestUtil.getRemoteInetAddress(request),
loggedActionType.getValue(),
loggedActionType.getActionContext().getUserId(),
Objects.toString(newState, ""),
@ -83,9 +80,9 @@ public class UserActionLogService extends HangarService {
public void organization(HttpServletRequest request, LoggedActionType<LoggedActionType.OrganizationContext> loggedActionType, String newState, String oldState) {
LoggedActionsOrganizationTable log = new LoggedActionsOrganizationTable(
getCurrentUser().getId(),
getInetAddress(request),
RequestUtil.getRemoteInetAddress(request),
loggedActionType.getValue(),
loggedActionType.getActionContext().getOrganizationLog(),
loggedActionType.getActionContext().getOrganizationId(),
Objects.toString(newState, ""),
Objects.toString(oldState, "")
);
@ -102,15 +99,5 @@ public class UserActionLogService extends HangarService {
}
return actionsDao.get().getLog(userFilter, projectFilter, versionFilter, pageFilter, actionFilter, subjectFilter, offset, pageSize);
}
private InetAddress getInetAddress(HttpServletRequest request) {
String host = request.getHeader("X-Forwarded-For");
if (host == null) host = request.getRemoteHost();
try {
return InetAddress.getByName(host);
} catch (UnknownHostException e) {
return null;
}
}
}

View File

@ -3,6 +3,8 @@ package io.papermc.hangar.service;
import io.papermc.hangar.config.CacheConfig;
import io.papermc.hangar.config.hangar.HangarConfig;
import io.papermc.hangar.controller.UsersController;
import io.papermc.hangar.db.customtypes.LoggedActionType;
import io.papermc.hangar.db.customtypes.LoggedActionType.UserContext;
import io.papermc.hangar.db.dao.HangarDao;
import io.papermc.hangar.db.dao.NotificationsDao;
import io.papermc.hangar.db.dao.OrganizationDao;
@ -60,12 +62,13 @@ public class UserService extends HangarService {
private final PermissionService permissionService;
private final OrgService orgService;
private final SessionService sessionService;
private final UserActionLogService userActionLogService;
private final HangarConfig config;
private final HttpServletRequest request;
@Autowired
public UserService(HangarDao<UserDao> userDao, HangarConfig config, HangarDao<OrganizationDao> orgDao, HangarDao<ProjectDao> projectDao, HangarDao<OrganizationDao> organizationDao, HangarDao<NotificationsDao> notificationsDao, RoleService roleService, PermissionService permissionService, OrgService orgService, SessionService sessionService, HttpServletRequest request) {
public UserService(HangarDao<UserDao> userDao, HangarConfig config, HangarDao<OrganizationDao> orgDao, HangarDao<ProjectDao> projectDao, HangarDao<OrganizationDao> organizationDao, HangarDao<NotificationsDao> notificationsDao, RoleService roleService, PermissionService permissionService, OrgService orgService, SessionService sessionService, UserActionLogService userActionLogService, HttpServletRequest request) {
this.userDao = userDao;
this.config = config;
this.orgDao = orgDao;
@ -76,6 +79,7 @@ public class UserService extends HangarService {
this.permissionService = permissionService;
this.orgService = orgService;
this.sessionService = sessionService;
this.userActionLogService = userActionLogService;
this.request = request;
}
@ -184,6 +188,11 @@ public class UserService extends HangarService {
public void setLocked(UsersTable user, boolean locked) {
user.setIsLocked(locked);
if (locked) {
userActionLogService.user(request, LoggedActionType.USER_LOCKED.with(UserContext.of(user.getId())), user.getName() + " is now locked", user.getName() + " was unlocked");
} else {
userActionLogService.user(request, LoggedActionType.USER_UNLOCKED.with(UserContext.of(user.getId())), user.getName() + " is now unlocked", user.getName() + " was locked");
}
userDao.get().update(user);
}

View File

@ -106,7 +106,7 @@
</td>
</#if>
<#-- Show diff only when page edit -->
<#if action.action.name == "ProjectPageEdited" || action.action == "VersionDescriptionEdited">
<#if action.action.name == "ProjectPageEdited" || action.action.name == "ProjectPageDeleted" || action.action.name == "ProjectPageCreated" || action.action.name == "VersionDescriptionEdited">
<td>
<a href="#" class="data-view-old" data-view="${offset + action?index}">View</a>
<textarea style="display: none" data-oldstate="${offset + action?index}">${action.oldState}</textarea>