work on api keys page (#56)

Co-authored-by: MiniDigger <admin@minidigger.me>
This commit is contained in:
Jake Potrebic 2020-07-31 02:41:36 -07:00 committed by GitHub
parent e56ec0ec27
commit daef6b18f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 168 additions and 54 deletions

View File

@ -1,12 +1,13 @@
package me.minidigger.hangar.controller;
import me.minidigger.hangar.model.viewhelpers.UserData;
import me.minidigger.hangar.service.ApiKeyService;
import me.minidigger.hangar.service.PermissionService;
import me.minidigger.hangar.db.customtypes.LoggedActionType;
import me.minidigger.hangar.db.customtypes.LoggedActionType.UserContext;
import me.minidigger.hangar.service.UserActionLogService;
import me.minidigger.hangar.util.AlertUtil;
import me.minidigger.hangar.util.AlertUtil.AlertType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.annotation.Secured;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@ -14,7 +15,6 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
@ -27,6 +27,7 @@ import me.minidigger.hangar.db.model.UsersTable;
import me.minidigger.hangar.service.AuthenticationService;
import me.minidigger.hangar.service.UserService;
import me.minidigger.hangar.util.RouteHelper;
import org.springframework.web.servlet.view.RedirectView;
@Controller
public class UsersController extends HangarController {
@ -37,15 +38,19 @@ public class UsersController extends HangarController {
private final UserService userService;
private final UserActionLogService userActionLogService;
private final RouteHelper routeHelper;
private final ApiKeyService apiKeyService;
private final PermissionService permissionService;
@Autowired
public UsersController(HangarConfig hangarConfig, HangarDao<UserDao> userDao, AuthenticationService authenticationService, ApplicationController applicationController, UserService userService, UserActionLogService userActionLogService, RouteHelper routeHelper) {
public UsersController(HangarConfig hangarConfig, HangarDao<UserDao> userDao, AuthenticationService authenticationService, ApplicationController applicationController, UserService userService, UserActionLogService userActionLogService, RouteHelper routeHelper, ApiKeyService apiKeyService, PermissionService permissionService) {
this.hangarConfig = hangarConfig;
this.userDao = userDao;
this.authenticationService = authenticationService;
this.userService = userService;
this.userActionLogService = userActionLogService;
this.routeHelper = routeHelper;
this.apiKeyService = apiKeyService;
this.permissionService = permissionService;
}
@RequestMapping("/authors")
@ -81,9 +86,9 @@ public class UsersController extends HangarController {
}
@RequestMapping("/logout")
public ModelAndView logout(HttpSession session) {
public RedirectView logout(HttpSession session) {
session.invalidate();
return new ModelAndView("redirect:/"); // TODO redirect to sso
return new RedirectView(routeHelper.getRouteUrl("showHome")); // TODO redirect to sso
}
@Secured("ROLE_USER")
@ -126,26 +131,30 @@ public class UsersController extends HangarController {
@RequestMapping("/{user}")
public ModelAndView showProjects(@PathVariable String user) {
UsersTable dbUser = userDao.get().getByName(user);
if (dbUser == null) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND);
}
ModelAndView mav = new ModelAndView("users/projects");
mav.addObject("u", userService.getUserData(dbUser));
mav.addObject("u", userService.getUserData(user));
mav.addObject("o", null);
return fillModel(mav);
}
@Secured("ROLE_USER")
@RequestMapping("/{user}/settings/apiKeys")
public Object editApiKeys(@PathVariable Object user) {
return null; // TODO implement editApiKeys request controller
@GetMapping("/{user}/settings/apiKeys")
public ModelAndView editApiKeys(@PathVariable String user) {
ModelAndView mav = new ModelAndView("users/apiKeys");
UserData userData = userService.getUserData(user);
long userId = userData.getUser().getId();
mav.addObject("u", userData);
mav.addObject("keys", apiKeyService.getKeysForUser(userId));
mav.addObject("perms", permissionService.getPossibleOrganizationPermissions(userId).add(permissionService.getPossibleProjectPermissions(userId)).add(userData.getUserPerm()).toNamed());
return fillModel(mav); // TODO implement editApiKeys request controller
}
@Secured("ROLE_USER")
@RequestMapping("/{user}/settings/lock/{locked}")
public Object setLocked(@PathVariable Object user, @PathVariable Object locked, @RequestParam Object sso, @RequestParam Object sig) {
return null; // TODO implement setLocked request controller
public RedirectView setLocked(@PathVariable String user, @PathVariable boolean locked, @RequestParam Object sso, @RequestParam Object sig) {
// TODO auth
userService.setLocked(user, locked);
return new RedirectView(routeHelper.getRouteUrl("users.showProjects", user));
}
@Secured("ROLE_USER")

View File

@ -0,0 +1,27 @@
package me.minidigger.hangar.db.dao;
import me.minidigger.hangar.db.model.ApiKeysTable;
import me.minidigger.hangar.model.Permission;
import org.jdbi.v3.sqlobject.config.RegisterBeanMapper;
import org.jdbi.v3.sqlobject.customizer.BindBean;
import org.jdbi.v3.sqlobject.customizer.Timestamped;
import org.jdbi.v3.sqlobject.statement.GetGeneratedKeys;
import org.jdbi.v3.sqlobject.statement.SqlQuery;
import org.jdbi.v3.sqlobject.statement.SqlUpdate;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
@RegisterBeanMapper(ApiKeysTable.class)
public interface ApiKeyDao {
@Timestamped
@GetGeneratedKeys
@SqlUpdate("INSERT INTO api_keys (created_at, name, owner_id, token_identifier, token, raw_key_permissions) VALUES (:now, :name, :ownerId, :tokenIdentifier, :token, :rawKeyPermissions)")
ApiKeysTable insert(@BindBean ApiKeysTable apiKeysTable);
@RegisterBeanMapper(value = Permission.class, prefix = "perm")
@SqlQuery("SELECT *, raw_key_permissions::BIGINT perm_value FROM api_keys WHERE owner_id = :ownerId")
List<ApiKeysTable> getByOwner(long ownerId);
}

View File

@ -6,9 +6,15 @@ import org.jdbi.v3.sqlobject.statement.SqlQuery;
import org.springframework.stereotype.Repository;
@Repository
@RegisterBeanMapper(value = Permission.class, prefix = "perm")
public interface PermissionsDao {
@RegisterBeanMapper(value = Permission.class, prefix = "perm")
@SqlQuery("SELECT gt.permission::bigint perm_value FROM global_trust gt JOIN users u on gt.user_id = u.id WHERE (u.id = :userId OR u.name = :userName)")
@SqlQuery("SELECT gt.permission::BIGINT perm_value FROM global_trust gt JOIN users u on gt.user_id = u.id WHERE (u.id = :userId OR u.name = :userName)")
Permission getGlobalPermission(Long userId, String userName);
@SqlQuery("SELECT coalesce(bit_or(r.permission), B'0'::BIT(64))::BIGINT perm_value FROM user_project_roles upr JOIN roles r ON upr.role_type = r.name WHERE upr.user_id = :userId")
Permission getPossibleProjectPermissions(long userId);
@SqlQuery("SELECT coalesce(bit_or(r.permission), B'0'::BIT(64))::BIGINT perm_value FROM user_organization_roles uor JOIN roles r ON uor.role_type = r.name WHERE uor.user_id = :userId")
Permission getPossibleOrganizationPermissions(long userId);
}

View File

@ -21,7 +21,7 @@ public interface UserGlobalRolesDao {
void insert(@BindBean UserGlobalRolesTable entry);
@AllowUnusedBindings
@SqlQuery("SELECT r.id, r.name, r.category, r.title, r.color, r.is_assignable, r.rank, r.permission " +
@SqlQuery("SELECT r.id, r.name, r.category, r.title, r.color, r.is_assignable, r.rank, r.permission::BIGINT " +
"FROM user_global_roles ugr " +
"JOIN roles r on ugr.role_id = r.id " +
"JOIN users u on ugr.user_id = u.id " +

View File

@ -1,6 +1,9 @@
package me.minidigger.hangar.db.model;
import me.minidigger.hangar.model.Permission;
import org.jdbi.v3.core.mapper.Nested;
import java.time.OffsetDateTime;
public class ApiKeysTable {
@ -11,7 +14,8 @@ public class ApiKeysTable {
private long ownerId;
private String tokenIdentifier;
private String token;
private long rawKeyPermissions;
@Nested("perm")
private Permission rawKeyPermissions;
public long getId() {
@ -68,11 +72,11 @@ public class ApiKeysTable {
}
public long getRawKeyPermissions() {
public Permission getRawKeyPermissions() {
return rawKeyPermissions;
}
public void setRawKeyPermissions(long rawKeyPermissions) {
public void setRawKeyPermissions(Permission rawKeyPermissions) {
this.rawKeyPermissions = rawKeyPermissions;
}

View File

@ -0,0 +1,27 @@
package me.minidigger.hangar.model.viewhelpers;
import me.minidigger.hangar.db.model.ApiKeysTable;
import me.minidigger.hangar.model.NamedPermission;
import me.minidigger.hangar.model.Permission;
import java.util.Collection;
public class ApiKey {
private ApiKeysTable key;
public ApiKey(ApiKeysTable key) {
this.key = key;
}
public ApiKeysTable getKey() {
return key;
}
public boolean getIsSubKey(Permission perm) {
return key.getRawKeyPermissions().has(perm);
}
public Collection<NamedPermission> getNamedRawPermissions() {
return key.getRawKeyPermissions().toNamed();
}
}

View File

@ -0,0 +1,27 @@
package me.minidigger.hangar.service;
import me.minidigger.hangar.db.dao.ApiKeyDao;
import me.minidigger.hangar.db.dao.HangarDao;
import me.minidigger.hangar.db.model.ApiKeysTable;
import me.minidigger.hangar.model.viewhelpers.ApiKey;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class ApiKeyService {
private final HangarDao<ApiKeyDao> apiKeyDao;
@Autowired
public ApiKeyService(HangarDao<ApiKeyDao> apiKeyDao) {
this.apiKeyDao = apiKeyDao;
}
public List<ApiKey> getKeysForUser(long userId) {
return apiKeyDao.get().getByOwner(userId).stream().map(ApiKey::new).collect(Collectors.toList());
}
}

View File

@ -23,4 +23,12 @@ public class PermissionService {
public Permission getGlobalPermission(String userName) {
return permissionsDao.get().getGlobalPermission(null, userName);
}
public Permission getPossibleProjectPermissions(long userId) {
return permissionsDao.get().getPossibleProjectPermissions(userId);
}
public Permission getPossibleOrganizationPermissions(long userId) {
return permissionsDao.get().getPossibleOrganizationPermissions(userId);
}
}

View File

@ -7,6 +7,7 @@ import me.minidigger.hangar.model.viewhelpers.Organization;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
@ -28,6 +29,7 @@ import me.minidigger.hangar.model.viewhelpers.HeaderData;
import me.minidigger.hangar.model.viewhelpers.Staff;
import me.minidigger.hangar.model.viewhelpers.UserData;
import me.minidigger.hangar.security.HangarAuthentication;
import org.springframework.web.server.ResponseStatusException;
@Service
public class UserService {
@ -125,7 +127,20 @@ public class UserService {
}
}
public void setLocked(String userName, boolean locked) {
UsersTable user = userDao.get().getByName(userName);
user.setIsLocked(locked);
userDao.get().update(user);
}
public UserData getUserData(String userName) {
return getUserData(userDao.get().getByName(userName));
}
public UserData getUserData(UsersTable user) {
if (user == null) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND);
}
// TODO getUserData
boolean isOrga = false;
int projectCount = projectDao.get().getProjectCountByUserId(user.getId());

View File

@ -1,16 +1,11 @@
<#import "/spring.ftl" as spring />
<#import "*/utils/hangar.ftlh" as hangar />
<#import "*/users/view.ftlh" as user />
<#--@import controllers.sugar.Requests.OreRequest
@import models.viewhelper.{OrganizationData, ScopedOrganizationData, UserData}
@import ore.OreConfig
@import ore.models.api.ApiKey
@import ore.permission.NamedPermission
@import views.html.helper.CSPNonce-->
<#import "*/users/view.ftlh" as userView />
<#--@(u: UserData, o: Option[(OrganizationData, ScopedOrganizationData)], keys: Seq[ApiKey], perms: Seq[NamedPermission])(implicit messages: Messages, flash: Flash, request: OreRequest[_], config: OreConfig, assetsFinder: AssetsFinder)-->
<#-- @ftlvariable name="u" type="me.minidigger.hangar.model.viewhelpers.UserData" -->
<#-- @ftlvariable name="keys" type="java.util.List<ApiKey>" -->
<#-- @ftlvariable name="perms" type="java.util.Collection<NamedPermission>" -->
<#assign scriptsVar>
<script type="text/javascript" src="<@hangar.url "javascripts/apiKeysManagement.js" />"></script>
<script <#--@CSPNonce.attr-->>
@ -22,7 +17,8 @@
</script>
</#assign>
<@user.view u=u o=o additionalScripts=scriptsVar>
<#assign NamedPermission=@helper["me.minidigger.hangar.model.NamedPermission"] />
<@userView.view u=u o=o additionalScripts=scriptsVar>
<div id="keyAlert" class="alert alert-danger" role="alert" style="display: none;"></div>
<div class="row">
@ -30,24 +26,18 @@
<h2><@spring.message "user.apiKeys.createNew" /></h2>
<div id="api-create-key-form">
<div class="row">
<div class="col-md-6">
<#list perms.take(NamedPermission.values.length / 2) as perm>
<div class="checkbox">
<label>
<input type="checkbox" id="perm.${perm.entryName}"> ${perm.entryName}
</label>
</div>
</#list>
</div>
<div class="col-md-6">
<#list perms.drop(NamedPermission.values.length / 2) as perm>
<div class="checkbox">
<label>
<input type="checkbox" id="perm.${perm.entryName}"> ${perm.entryName}
</label>
</div>
</#list>
</div>
<#list perms?chunk( (perms?size/2)?int ) as chunk>
<div class="col-md-6">
<#list chunk as perm>
<div class="checkbox">
<label>
<input type="checkbox" id="perm.${perm.name()}"> ${perm.name()}
</label>
</div>
</#list>
</div>
</#list>
</div>
<div class="form-group">
@ -71,12 +61,13 @@
</tr>
</thead>
<tbody id="api-key-rows">
<#-- @ftlvariable name="key" type="me.minidigger.hangar.model.viewhelpers.ApiKey" -->
<#list keys as key>
<tr class="api-key-row">
<th class="api-key-name">${key.name}</th>
<th class="api-key-name">${key.key.name}</th>
<th></th>
<th>${key.tokenIdentifier}</th>
<th>@key.namedRawPermissions.map(_.entryName).mkString(", ")</th>
<th>${key.key.tokenIdentifier}</th>
<th>${key.namedRawPermissions?map(np -> np.name())?join(", ")}</th>
<th><button class="btn btn-danger api-key-row-delete-button"><@spring.message "user.apiKeys.keyDeleteButton" /></button></th>
</tr>
</#list>
@ -84,4 +75,4 @@
</table>
</div>
</div>
</@user.view>
</@userView.view>