/** * ============================================================================= * * ORCID (R) Open Source * http://orcid.org * * Copyright (c) 2012-2014 ORCID, Inc. * Licensed under an MIT-Style License (MIT) * http://orcid.org/open-source-license * * This copyright and license information (including a link to the full license) * shall be included in its entirety in all copies or substantial portion of * the software. * * ============================================================================= */ package org.orcid.frontend.web.controllers; import java.net.MalformedURLException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import org.orcid.core.exception.OrcidClientGroupManagementException; import org.orcid.core.manager.ClientDetailsManager; import org.orcid.core.manager.OrcidClientGroupManager; import org.orcid.core.manager.OrcidSSOManager; import org.orcid.core.manager.ProfileEntityCacheManager; import org.orcid.core.manager.ProfileEntityManager; import org.orcid.core.manager.ThirdPartyLinkManager; import org.orcid.jaxb.model.clientgroup.OrcidClient; import org.orcid.jaxb.model.clientgroup.OrcidClientGroup; import org.orcid.jaxb.model.clientgroup.RedirectUriType; import org.orcid.jaxb.model.message.ErrorDesc; import org.orcid.jaxb.model.message.OrcidProfile; import org.orcid.jaxb.model.message.OrcidType; import org.orcid.jaxb.model.message.ScopePathType; import org.orcid.persistence.jpa.entities.ClientDetailsEntity; import org.orcid.persistence.jpa.entities.ProfileEntity; import org.orcid.pojo.ajaxForm.Checkbox; import org.orcid.pojo.ajaxForm.Client; import org.orcid.pojo.ajaxForm.PojoUtil; import org.orcid.pojo.ajaxForm.RedirectUri; import org.orcid.pojo.ajaxForm.Text; import org.orcid.utils.OrcidStringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.servlet.ModelAndView; /** * @author Angel Montenegro Date: 20/06/2013 */ @Controller @RequestMapping(value = "/group/developer-tools") @PreAuthorize("!@sourceManager.isInDelegationMode() OR @sourceManager.isDelegatedByAnAdmin()") public class GroupAdministratorController extends BaseWorkspaceController { private static final Logger LOGGER = LoggerFactory.getLogger(GroupAdministratorController.class); @Resource OrcidClientGroupManager orcidClientGroupManager; @Resource private OrcidSSOManager orcidSSOManager; @Resource private ThirdPartyLinkManager thirdPartyLinkManager; @Resource private ClientDetailsManager clientDetailsManager; @Resource private ProfileEntityManager profileEntityManager; @Resource(name = "profileEntityCacheManager") ProfileEntityCacheManager profileEntityCacheManager; @RequestMapping public ModelAndView manageClients() { ModelAndView mav = new ModelAndView("member_developer_tools"); OrcidProfile profile = getEffectiveProfile(); if (profile.getType() == null || !profile.getType().equals(OrcidType.GROUP)) { LOGGER.warn("Trying to access group/developer-tools page with user {} which is not a group", profile.getOrcidIdentifier().getPath()); return new ModelAndView("redirect:/my-orcid"); } OrcidClientGroup group = orcidClientGroupManager.retrieveOrcidClientGroup(profile.getOrcidIdentifier().getPath()); mav.addObject("group", group); switch (profile.getGroupType()) { case BASIC: mav.addObject("clientType", "UPDATER"); break; case PREMIUM: mav.addObject("clientType", "PREMIUM_UPDATER"); break; case BASIC_INSTITUTION: mav.addObject("clientType", "CREATOR"); break; case PREMIUM_INSTITUTION: mav.addObject("clientType", "PREMIUM_CREATOR"); break; } return mav; } @RequestMapping(value = "/get-empty-redirect-uri.json", method = RequestMethod.GET) public @ResponseBody RedirectUri getEmptyRedirectUri(HttpServletRequest request) { RedirectUri result = new RedirectUri(); result.setValue(new Text()); result.setType(Text.valueOf(RedirectUriType.DEFAULT.value())); result.setActType(Text.valueOf("")); result.setGeoArea(Text.valueOf("")); return result; } @RequestMapping(value = "/client.json", method = RequestMethod.GET) public @ResponseBody Client getClient() { Client emptyClient = new Client(); emptyClient.setDisplayName(new Text()); emptyClient.setWebsite(new Text()); emptyClient.setShortDescription(new Text()); emptyClient.setClientId(new Text()); emptyClient.setClientSecret(new Text()); emptyClient.setType(new Text()); emptyClient.setAllowAutoDeprecate(Checkbox.valueOf(false)); ArrayList<RedirectUri> redirectUris = new ArrayList<RedirectUri>(); RedirectUri emptyRedirectUri = new RedirectUri(); emptyRedirectUri.setValue(new Text()); emptyRedirectUri.setType(Text.valueOf(RedirectUriType.DEFAULT.value())); emptyRedirectUri.setActType(Text.valueOf("")); emptyRedirectUri.setGeoArea(Text.valueOf("")); redirectUris.add(emptyRedirectUri); emptyClient.setRedirectUris(redirectUris); return emptyClient; } private boolean validateUrl(String url, boolean checkProtocol) { String urlToCheck = null; if (PojoUtil.isEmpty(url)) return false; // To validate the URL we need a string with a protocol, so, check if it // have it, if it doesn't, add it. // Check if the URL begins with the protocol if(checkProtocol) { urlToCheck = url; } else { if (url.startsWith("http://") || url.startsWith("https://")) { urlToCheck = url; } else { // If it doesn't, add the http protocol by default urlToCheck = "http://" + url; } } try { new java.net.URL(urlToCheck); } catch (MalformedURLException e) { return false; } return true; } public Client validateDisplayName(Client client) { client.getDisplayName().setErrors(new ArrayList<String>()); if (PojoUtil.isEmpty(client.getDisplayName())) { setError(client.getDisplayName(), "manage.developer_tools.group.error.display_name.empty"); } else if (client.getDisplayName().getValue().length() > 150) { setError(client.getDisplayName(), "manage.developer_tools.group.error.display_name.150"); } else { if(OrcidStringUtils.hasHtml(client.getDisplayName().getValue())) setError(client.getDisplayName(), "manage.developer_tools.group.error.display_name.html"); } return client; } public Client validateWebsite(Client client) { client.getWebsite().setErrors(new ArrayList<String>()); if (PojoUtil.isEmpty(client.getWebsite())) { setError(client.getWebsite(), "manage.developer_tools.group.error.website.empty"); } else if (!validateUrl(client.getWebsite().getValue())) { setError(client.getWebsite(), "common.invalid_url"); } return client; } public Client validateShortDescription(Client client) { client.getShortDescription().setErrors(new ArrayList<String>()); if (PojoUtil.isEmpty(client.getShortDescription())) setError(client.getShortDescription(), "manage.developer_tools.group.error.short_description.empty"); else { if(OrcidStringUtils.hasHtml(client.getShortDescription().getValue())) setError(client.getShortDescription(), "manage.developer_tools.group.error.short_description.html"); } return client; } public Client validateRedirectUris(Client client) { return validateRedirectUris(client, false); } public Client validateRedirectUris(Client client, boolean checkProtocol) { if (client.getRedirectUris() != null && client.getRedirectUris().size() > 0) { for (RedirectUri redirectUri : client.getRedirectUris()) { redirectUri.setErrors(new ArrayList<String>()); if (!validateUrl(redirectUri.getValue().getValue(), checkProtocol)) { setError(redirectUri, "common.invalid_url"); } if (RedirectUriType.DEFAULT.value().equals(redirectUri.getType().getValue())) { // Clean all scopes from default redirect uri type if (redirectUri.getScopes() != null && !redirectUri.getScopes().isEmpty()) { redirectUri.setScopes(new ArrayList<String>()); } } else { if (redirectUri.getScopes() != null && redirectUri.getScopes().isEmpty()) { //If the redirect type is not default, the scopes must not be emtpy setError(redirectUri, "manage.developer_tools.group.error.empty_scopes"); } } } } return client; } @RequestMapping(value = "/add-client.json", method = RequestMethod.POST) @Produces(value = { MediaType.APPLICATION_JSON }) public @ResponseBody Client createClient(@RequestBody Client client) { // Clean the error list client.setErrors(new ArrayList<String>()); // Validate fields validateDisplayName(client); validateWebsite(client); validateShortDescription(client); validateRedirectUris(client); copyErrors(client.getDisplayName(), client); copyErrors(client.getWebsite(), client); copyErrors(client.getShortDescription(), client); for (RedirectUri redirectUri : client.getRedirectUris()) { copyErrors(redirectUri, client); } if (client.getErrors().size() == 0) { OrcidProfile profile = getEffectiveProfile(); String groupOrcid = profile.getOrcidIdentifier().getPath(); if (profile.getType() == null || !profile.getType().equals(OrcidType.GROUP)) { LOGGER.warn("Trying to create client with non group user {}", profile.getOrcidIdentifier().getPath()); throw new OrcidClientGroupManagementException(getMessage("web.orcid.privilege.exception")); } OrcidClient result = null; try { result = orcidClientGroupManager.createAndPersistClientProfile(groupOrcid, client.toOrcidClient()); } catch (OrcidClientGroupManagementException e) { LOGGER.error(e.getMessage()); result = new OrcidClient(); result.setErrors(new ErrorDesc(getMessage("manage.developer_tools.group.cannot_create_client"))); } client = Client.valueOf(result); } return client; } @RequestMapping(value = "/edit-client.json", method = RequestMethod.POST) @Produces(value = { MediaType.APPLICATION_JSON }) public @ResponseBody Client editClient(@RequestBody Client client) { // Clean the error list client.setErrors(new ArrayList<String>()); // Validate fields validateDisplayName(client); validateWebsite(client); validateShortDescription(client); validateRedirectUris(client); copyErrors(client.getDisplayName(), client); copyErrors(client.getWebsite(), client); copyErrors(client.getShortDescription(), client); for (RedirectUri redirectUri : client.getRedirectUris()) { copyErrors(redirectUri, client); } if (client.getErrors().size() == 0) { OrcidProfile profile = getEffectiveProfile(); String groupOrcid = profile.getOrcidIdentifier().getPath(); if (profile.getType() == null || !profile.getType().equals(OrcidType.GROUP)) { LOGGER.warn("Trying to edit client with non group user {}", profile.getOrcidIdentifier().getPath()); throw new OrcidClientGroupManagementException(getMessage("web.orcid.privilege.exception")); } OrcidClient result = null; try { result = orcidClientGroupManager.updateClient(groupOrcid, client.toOrcidClient()); clearCache(); } catch (OrcidClientGroupManagementException e) { LOGGER.error(e.getMessage()); result = new OrcidClient(); result.setErrors(new ErrorDesc(getMessage("manage.developer_tools.group.unable_to_update"))); } client = Client.valueOf(result); } return client; } @RequestMapping(value = "/get-clients.json", method = RequestMethod.GET) @Produces(value = { MediaType.APPLICATION_JSON }) public @ResponseBody List<Client> getClients() { OrcidProfile profile = getEffectiveProfile(); String groupOrcid = profile.getOrcidIdentifier().getPath(); if (profile.getType() == null || !profile.getType().equals(OrcidType.GROUP)) { LOGGER.warn("Trying to get clients of non group user {}", profile.getOrcidIdentifier().getPath()); throw new OrcidClientGroupManagementException(getMessage("web.orcid.privilege.exception")); } OrcidClientGroup group = orcidClientGroupManager.retrieveOrcidClientGroup(groupOrcid); List<Client> clients = new ArrayList<Client>(); for (OrcidClient orcidClient : group.getOrcidClient()) { clients.add(Client.valueOf(orcidClient)); } return clients; } @ModelAttribute("redirectUriTypes") public Map<String, String> getRedirectUriTypes() { Map<String, String> redirectUriTypes = new LinkedHashMap<String, String>(); for (RedirectUriType rType : RedirectUriType.values()) { if (!RedirectUriType.SSO_AUTHENTICATION.equals(rType)) redirectUriTypes.put(rType.value(), rType.value()); } return redirectUriTypes; } /** * Since the groups have changed, the cache version must be updated on * database and all caches have to be evicted. * */ public void clearCache() { // Updates cache database version thirdPartyLinkManager.updateDatabaseCacheVersion(); // Evict current cache thirdPartyLinkManager.evictAll(); } @RequestMapping(value = "/get-available-scopes.json", method = RequestMethod.GET) @Produces(value = { MediaType.APPLICATION_JSON }) public @ResponseBody List<String> getAvailableRedirectUriScopes() { List<String> scopes = new ArrayList<String>(); // Ignore these scopes List<ScopePathType> ignoreScopes = new ArrayList<ScopePathType>(Arrays.asList(ScopePathType.ORCID_PATENTS_CREATE, ScopePathType.ORCID_PATENTS_READ_LIMITED, ScopePathType.ORCID_PATENTS_UPDATE, ScopePathType.WEBHOOK, ScopePathType.ORCID_PROFILE_CREATE, ScopePathType.FUNDING_READ_LIMITED, ScopePathType.AFFILIATIONS_READ_LIMITED, ScopePathType.READ_PUBLIC)); for (ScopePathType t : ScopePathType.values()) { if (!ignoreScopes.contains(t)) scopes.add(t.value()); } Collections.sort(scopes); return scopes; } /** * Reset client secret * */ @RequestMapping(value = "/reset-client-secret.json", method = RequestMethod.POST) public @ResponseBody boolean resetClientSecret(@RequestBody String clientId) { //Verify this client belongs to the member ClientDetailsEntity clientDetails = clientDetailsManager.findByClientId(clientId); if(clientDetails == null) return false; ProfileEntity groupProfile = profileEntityCacheManager.retrieve(clientDetails.getGroupProfileId()); if(groupProfile == null) return false; if(!groupProfile.getId().equals(getCurrentUserOrcid())) return false; return orcidSSOManager.resetClientSecret(clientId); } }