diff --git a/src/main/java/io/papermc/hangar/controller/OrgController.java b/src/main/java/io/papermc/hangar/controller/OrgController.java index 5728f1098..899db5965 100644 --- a/src/main/java/io/papermc/hangar/controller/OrgController.java +++ b/src/main/java/io/papermc/hangar/controller/OrgController.java @@ -1,5 +1,6 @@ package io.papermc.hangar.controller; +import com.fasterxml.jackson.core.JsonProcessingException; import io.papermc.hangar.config.hangar.HangarConfig; import io.papermc.hangar.db.model.OrganizationsTable; import io.papermc.hangar.db.model.UserOrganizationRolesTable; @@ -7,6 +8,7 @@ import io.papermc.hangar.db.model.UsersTable; import io.papermc.hangar.model.NotificationType; import io.papermc.hangar.model.Role; import io.papermc.hangar.model.viewhelpers.UserData; +import io.papermc.hangar.service.AuthenticationService; import io.papermc.hangar.service.NotificationService; import io.papermc.hangar.service.OrgFactory; import io.papermc.hangar.service.OrgService; @@ -32,6 +34,7 @@ import org.springframework.web.server.ResponseStatusException; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.support.RedirectAttributes; +import java.net.URI; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -39,6 +42,7 @@ import java.util.Map; @Controller public class OrgController extends HangarController { + private final AuthenticationService authenticationService; private final OrgService orgService; private final OrgFactory orgFactory; private final UserService userService; @@ -48,7 +52,8 @@ public class OrgController extends HangarController { private final RouteHelper routeHelper; @Autowired - public OrgController(OrgService orgService, OrgFactory orgFactory, UserService userService, RoleService roleService, NotificationService notificationService, HangarConfig hangarConfig, RouteHelper routeHelper) { + public OrgController(AuthenticationService authenticationService, OrgService orgService, OrgFactory orgFactory, UserService userService, RoleService roleService, NotificationService notificationService, HangarConfig hangarConfig, RouteHelper routeHelper) { + this.authenticationService = authenticationService; this.orgService = orgService; this.orgFactory = orgFactory; this.userService = userService; @@ -123,8 +128,16 @@ public class OrgController extends HangarController { @Secured("ROLE_USER") @GetMapping("/organizations/{organization}/settings/avatar") - public Object updateAvatar(@PathVariable Object organization) { - return null; // TODO implement updateAvatar request controller + @PreAuthorize("@authenticationService.authOrgRequest(T(io.papermc.hangar.model.Permission).EditOrganizationSettings, #organization, true)") + public ModelAndView updateAvatar(@PathVariable String organization) { + try { + URI uri = authenticationService.changeAvatarUri(userService.getCurrentUser().getName(), organization); + return new ModelAndView("redirect:" + uri.toString()); + } catch (JsonProcessingException e) { + ModelAndView mav = new ModelAndView("redirect:" + routeHelper.getRouteUrl("users.showProjects", organization)); + AlertUtil.showAlert(mav, AlertType.ERROR, "organization.avatarFailed"); + return mav; + } } @Secured("ROLE_USER") diff --git a/src/main/java/io/papermc/hangar/service/AuthenticationService.java b/src/main/java/io/papermc/hangar/service/AuthenticationService.java index d23855271..32474a25f 100644 --- a/src/main/java/io/papermc/hangar/service/AuthenticationService.java +++ b/src/main/java/io/papermc/hangar/service/AuthenticationService.java @@ -1,5 +1,8 @@ package io.papermc.hangar.service; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import io.papermc.hangar.config.hangar.HangarConfig; import io.papermc.hangar.controller.util.ApiScope; import io.papermc.hangar.db.dao.ApiKeyDao; @@ -16,19 +19,28 @@ import io.papermc.hangar.model.Role; import io.papermc.hangar.model.generated.ApiSessionResponse; import io.papermc.hangar.model.generated.SessionType; import io.papermc.hangar.security.HangarAuthentication; +import io.papermc.hangar.service.sso.ChangeAvatarToken; import io.papermc.hangar.util.AuthUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; import org.springframework.web.context.annotation.RequestScope; import org.springframework.web.server.ResponseStatusException; +import org.springframework.web.util.UriComponentsBuilder; import javax.servlet.http.HttpServletRequest; +import java.net.URI; import java.time.OffsetDateTime; import java.util.List; import java.util.UUID; @@ -48,12 +60,14 @@ public class AuthenticationService { private final PermissionService permissionService; private final OrgService orgService; private final UserService userService; + private final RestTemplate restTemplate; + private final ObjectMapper objectMapper; private static final String UUID_REGEX = "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"; private static final Pattern API_KEY_PATTERN = Pattern.compile("(" + UUID_REGEX + ").(" + UUID_REGEX + ")"); @Autowired - public AuthenticationService(HttpServletRequest request, ApiAuthInfo apiAuthInfo, HangarConfig hangarConfig, HangarDao userDao, HangarDao sessionsDao, HangarDao apiKeyDao, AuthenticationManager authenticationManager, RoleService roleService, PermissionService permissionService, OrgService orgService, UserService userService) { + public AuthenticationService(HttpServletRequest request, ApiAuthInfo apiAuthInfo, HangarConfig hangarConfig, HangarDao userDao, HangarDao sessionsDao, HangarDao apiKeyDao, AuthenticationManager authenticationManager, RoleService roleService, PermissionService permissionService, OrgService orgService, UserService userService, RestTemplate restTemplate, ObjectMapper objectMapper) { this.request = request; this.apiAuthInfo = apiAuthInfo; this.hangarConfig = hangarConfig; @@ -65,6 +79,8 @@ public class AuthenticationService { this.permissionService = permissionService; this.orgService = orgService; this.userService = userService; + this.restTemplate = restTemplate; + this.objectMapper = objectMapper; } public ApiAuthInfo getApiAuthInfo(String token) { @@ -219,4 +235,22 @@ public class AuthenticationService { SecurityContextHolder.getContext().setAuthentication(auth); } + public URI changeAvatarUri(String requester, String organization) throws JsonProcessingException { + ChangeAvatarToken token = getChangeAvatarToken(requester, organization); + UriComponentsBuilder uriComponents = UriComponentsBuilder.fromHttpUrl(hangarConfig.getAuthUrl()); + uriComponents.path("/accounts/user/{organization}/change-avatar/").queryParam("key", token.getSignedData()); + return uriComponents.build(organization); + } + + public ChangeAvatarToken getChangeAvatarToken(String requester, String organization) throws JsonProcessingException { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + MultiValueMap bodyMap = new LinkedMultiValueMap<>(); + bodyMap.add("api-key", hangarConfig.sso.getApiKey()); + bodyMap.add("request_username", requester); + ChangeAvatarToken token; + token = objectMapper.treeToValue(restTemplate.postForObject(hangarConfig.security.api.getUrl() + "/api/users/" + organization + "/change-avatar-token/", new HttpEntity<>(bodyMap, headers), ObjectNode.class), ChangeAvatarToken.class); + return token; + } + } diff --git a/src/main/java/io/papermc/hangar/service/sso/ChangeAvatarToken.java b/src/main/java/io/papermc/hangar/service/sso/ChangeAvatarToken.java new file mode 100644 index 000000000..95e5e24b3 --- /dev/null +++ b/src/main/java/io/papermc/hangar/service/sso/ChangeAvatarToken.java @@ -0,0 +1,48 @@ +package io.papermc.hangar.service.sso; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.databind.node.ObjectNode; + +public class ChangeAvatarToken { + + private final String signedData; + private final String targetUsername; + private final int requestUserId; + + public ChangeAvatarToken(String signedData, String targetUsername, int requestUserId) { + this.signedData = signedData; + this.targetUsername = targetUsername; + this.requestUserId = requestUserId; + } + + public String getSignedData() { + return signedData; + } + + public String getTargetUsername() { + return targetUsername; + } + + public int getRequestUserId() { + return requestUserId; + } + + @JsonCreator + public static ChangeAvatarToken of(ObjectNode objectNode) { + ObjectNode rawData = (ObjectNode) objectNode.get("raw_data"); + return new ChangeAvatarToken( + objectNode.get("signed_data").asText(), + rawData.get("target_username").asText(), + rawData.get("request_user_id").asInt() + ); + } + + @Override + public String toString() { + return "ChangeAvatarToken{" + + "signedData='" + signedData + '\'' + + ", targetUsername='" + targetUsername + '\'' + + ", requestUserId=" + requestUserId + + '}'; + } +}